Fuzz HPACK

This commit is contained in:
Carl Lerche
2017-06-09 12:06:38 -07:00
parent 42f19f3006
commit fa53d9556b
6 changed files with 568 additions and 55 deletions

View File

@@ -3,18 +3,412 @@ extern crate hex;
extern crate walkdir;
extern crate serde;
extern crate serde_json;
extern crate quickcheck;
extern crate rand;
use super::{Header, Decoder, Encoder};
use super::{Header, Decoder, Encoder, Encode};
use http::header::{HeaderName, HeaderValue};
use self::bytes::BytesMut;
use self::hex::FromHex;
use self::serde_json::Value;
use self::walkdir::WalkDir;
use self::quickcheck::{QuickCheck, Arbitrary, Gen, TestResult};
use self::rand::{StdRng, Rng, SeedableRng};
use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::str;
const MAX_CHUNK: usize = 2 * 1024;
#[test]
fn hpack_fuzz_failing_1() {
FuzzHpack::new_reduced([
12433181738898983662,
14102727336666980714,
6105092270172216412,
16258270543720336235,
], 1).run();
}
#[test]
fn hpack_fuzz() {
fn prop(fuzz: FuzzHpack) -> TestResult {
fuzz.run();
TestResult::from_bool(true)
}
QuickCheck::new()
.tests(5000)
.quickcheck(prop as fn(FuzzHpack) -> TestResult)
}
#[derive(Debug, Clone)]
struct FuzzHpack {
// The magic seed that makes the test case reproducible
seed: [usize; 4],
// The set of headers to encode / decode
headers: Vec<Header>,
// The list of chunk sizes to do it in
chunks: Vec<usize>,
// Number of times reduced
reduced: usize,
}
impl FuzzHpack {
fn new_reduced(seed: [usize; 4], i: usize) -> FuzzHpack {
let mut ret = FuzzHpack::new(seed);
ret.headers.drain(i..);
ret
}
fn new(seed: [usize; 4]) -> FuzzHpack {
// Seed the RNG
let mut rng = StdRng::from_seed(&seed);
// Generates a bunch of source headers
let mut source: Vec<Header> = vec![];
for _ in 0..2000 {
source.push(gen_header(&mut rng));
}
// Actual test run headers
let num: usize = rng.gen_range(40, 300);
let mut actual: Vec<Header> = vec![];
let skew: i32 = rng.gen_range(1, 5);
while actual.len() < num {
let x: f64 = rng.gen_range(0.0, 1.0);
let x = x.powi(skew);
let i = (x * source.len() as f64) as usize;
actual.push(source[i].clone());
}
// Now, generate the buffer sizes used to encode
let mut chunks = vec![];
for _ in 0..rng.gen_range(0, 100) {
chunks.push(rng.gen_range(0, MAX_CHUNK));
}
FuzzHpack {
seed: seed,
headers: actual,
chunks: chunks,
reduced: 0,
}
}
fn run(mut self) {
let mut chunks = self.chunks;
let mut headers = self.headers;
let mut expect = headers.clone();
let mut encoded = vec![];
let mut buf = BytesMut::with_capacity(
chunks.pop().unwrap_or(MAX_CHUNK));
let mut index = None;
let mut input = headers.into_iter();
let mut encoder = Encoder::default();
let mut decoder = Decoder::default();
loop {
match encoder.encode(index.take(), &mut input, &mut buf).unwrap() {
Encode::Full => break,
Encode::Partial(i) => {
index = Some(i);
encoded.push(buf);
buf = BytesMut::with_capacity(
chunks.pop().unwrap_or(MAX_CHUNK));
}
}
}
// Decode
for buf in encoded {
decoder.decode(&buf.into(), |e| {
assert_eq!(e, expect.remove(0));
});
}
assert_eq!(0, expect.len());
}
}
impl Arbitrary for FuzzHpack {
fn arbitrary<G: Gen>(g: &mut G) -> Self {
FuzzHpack::new(quickcheck::Rng::gen(g))
}
fn shrink(&self) -> Box<Iterator<Item=FuzzHpack>> {
let s = self.clone();
let iter = (1..self.headers.len()).map(move |i| {
let mut r = s.clone();
r.headers.drain(i..);
r.reduced = i;
r
});
Box::new(iter)
}
}
fn gen_header(g: &mut StdRng) -> Header {
use http::StatusCode;
use http::method::{self, Method};
if g.gen_weighted_bool(10) {
match g.next_u32() % 5 {
0 => {
let value = gen_string(g, 4, 20);
Header::Authority(value.into())
}
1 => {
let method = match g.next_u32() % 6 {
0 => method::GET,
1 => method::POST,
2 => method::PUT,
3 => method::PATCH,
4 => method::DELETE,
5 => {
let n: usize = g.gen_range(3, 7);
let bytes: Vec<u8> = (0..n).map(|_| {
g.choose(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ").unwrap().clone()
}).collect();
Method::from_bytes(&bytes).unwrap()
}
_ => unreachable!(),
};
Header::Method(method)
}
2 => {
let value = match g.next_u32() % 2 {
0 => "http",
1 => "https",
_ => unreachable!(),
};
Header::Scheme(value.into())
}
3 => {
let value = match g.next_u32() % 100 {
0 => "/".to_string(),
1 => "/index.html".to_string(),
_ => gen_string(g, 2, 20),
};
Header::Path(value.into())
}
4 => {
let status = (g.gen::<u16>() % 500) + 100;
Header::Status(StatusCode::from_u16(status).unwrap())
}
_ => unreachable!(),
}
} else {
let name = gen_header_name(g);
let mut value = gen_header_value(g);
if g.gen_weighted_bool(30) {
value.set_sensitive(true);
}
Header::Field { name: name, value: value }
}
}
fn gen_header_name(g: &mut StdRng) -> HeaderName {
use http::header;
if g.gen_weighted_bool(2) {
g.choose(&[
// TODO: more headers
header::ACCEPT,
]).unwrap().clone()
} else {
let value = gen_string(g, 1, 25);
HeaderName::from_bytes(value.as_bytes()).unwrap()
}
}
fn gen_header_value(g: &mut StdRng) -> HeaderValue {
let value = gen_string(g, 0, 70);
HeaderValue::try_from_bytes(value.as_bytes()).unwrap()
}
fn gen_string(g: &mut StdRng, min: usize, max: usize) -> String {
let bytes: Vec<_> = (min..max).map(|_| {
// Chars to pick from
g.choose(b"ABCDEFGHIJKLMNOPQRSTUVabcdefghilpqrstuvwxyz----").unwrap().clone()
}).collect();
String::from_utf8(bytes).unwrap()
}
/*
impl Arbitrary for HeaderSet {
fn arbitrary<G: Gen>(g: &mut G) -> Self {
let mut source: Vec<Header> = vec![];
for _ in 0..2000 {
source.push(Header::arbitrary(g));
}
// Actual headers
let num: usize = g.gen_range(40, 300);
let mut actual: Vec<Header> = vec![];
let skew: i32 = g.gen_range(1, 5);
while actual.len() < num {
let x: f64 = g.gen_range(0.0, 1.0);
let x = x.powi(skew);
let i = (x * source.len() as f64) as usize;
actual.push(source[i].clone());
}
HeaderSet {
headers: actual,
}
}
fn shrink(&self) -> Box<Iterator<Item=HeaderSet>> {
let headers = self.headers.clone();
let iter = (0..headers.len()+1).map(move |i| {
HeaderSet { headers: headers[..0].to_vec() }
});
Box::new(iter)
}
}
impl Arbitrary for Header {
fn arbitrary<G: Gen>(g: &mut G) -> Self {
use http::StatusCode;
use http::method::{self, Method};
if g.gen_weighted_bool(10) {
match g.next_u32() % 5 {
0 => {
let value = String::arbitrary(g);
Header::Authority(value.into())
}
1 => {
let method = match g.next_u32() % 6 {
0 => method::GET,
1 => method::POST,
2 => method::PUT,
3 => method::PATCH,
4 => method::DELETE,
5 => {
let n = g.gen::<usize>() % 7;
let bytes: Vec<u8> = (0..n).map(|_| {
g.choose(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ").unwrap().clone()
}).collect();
Method::from_bytes(&bytes).unwrap()
}
_ => unreachable!(),
};
Header::Method(method)
}
2 => {
let value = match g.next_u32() % 2 {
0 => "http",
1 => "https",
_ => unreachable!(),
};
Header::Scheme(value.into())
}
3 => {
let value = match g.next_u32() % 100 {
0 => "/".to_string(),
1 => "/index.html".to_string(),
_ => String::arbitrary(g),
};
Header::Path(value.into())
}
4 => {
let status = (g.gen::<u16>() % 500) + 100;
Header::Status(StatusCode::from_u16(status).unwrap())
}
_ => unreachable!(),
}
} else {
let mut name = HeaderName2::arbitrary(g);
let mut value = HeaderValue2::arbitrary(g);
if g.gen_weighted_bool(30) {
value.0.set_sensitive(true);
}
Header::Field { name: name.0, value: value.0 }
}
}
}
#[derive(Clone)]
struct HeaderName2(HeaderName);
#[derive(Clone)]
struct HeaderValue2(HeaderValue);
impl Arbitrary for HeaderName2 {
fn arbitrary<G: Gen>(g: &mut G) -> Self {
use http::header;
if g.gen_weighted_bool(2) {
g.choose(&[
HeaderName2(header::ACCEPT),
]).unwrap().clone()
} else {
let len = g.gen::<usize>() % 25 + 1;
let value: Vec<u8> = (0..len).map(|_| {
g.choose(b"abcdefghijklmnopqrstuvwxyz-").unwrap().clone()
}).collect();
HeaderName2(HeaderName::from_bytes(&value).unwrap())
}
}
}
impl Arbitrary for HeaderValue2 {
fn arbitrary<G: Gen>(g: &mut G) -> Self {
// Random length
let len = g.gen::<usize>() % 70;
// Generate the value
let value: Vec<u8> = (0..len).map(|_| {
g.choose(b"abcdefghijklmnopqrstuvwxyz -_").unwrap().clone()
}).collect();
HeaderValue2(HeaderValue::try_from_bytes(&value).unwrap())
}
}
*/
#[test]
fn hpack_fixtures() {
@@ -129,7 +523,7 @@ fn test_story(story: Value) {
Header::new(name.clone().into(), value.clone().into()).unwrap()
}).collect();
encoder.encode(input.clone(), &mut buf).unwrap();
encoder.encode(None, &mut input.clone().into_iter(), &mut buf).unwrap();
decoder.decode(&buf.into(), |e| {
assert_eq!(e, input.remove(0));