209 lines
5.3 KiB
Rust
209 lines
5.3 KiB
Rust
#![no_main]
|
|
|
|
use libfuzzer_sys::arbitrary;
|
|
use libfuzzer_sys::arbitrary::Arbitrary;
|
|
use libfuzzer_sys::fuzz_target;
|
|
|
|
use aero_dav::{realization, types, xml};
|
|
use quick_xml::reader::NsReader;
|
|
use tokio::io::AsyncWriteExt;
|
|
use tokio::runtime::Runtime;
|
|
|
|
// Split this file
|
|
const tokens: [&str; 63] = [
|
|
"0",
|
|
"1",
|
|
"activelock",
|
|
"allprop",
|
|
"encoding",
|
|
"utf-8",
|
|
"http://ns.example.com/boxschema/",
|
|
"HTTP/1.1 200 OK",
|
|
"1997-12-01T18:27:21-08:00",
|
|
"Mon, 12 Jan 1998 09:25:56 GMT",
|
|
"\"abcdef\"",
|
|
"cannot-modify-protected-property",
|
|
"collection",
|
|
"creationdate",
|
|
"DAV:",
|
|
"D",
|
|
"C",
|
|
"xmlns:D",
|
|
"depth",
|
|
"displayname",
|
|
"error",
|
|
"exclusive",
|
|
"getcontentlanguage",
|
|
"getcontentlength",
|
|
"getcontenttype",
|
|
"getetag",
|
|
"getlastmodified",
|
|
"href",
|
|
"include",
|
|
"Infinite",
|
|
"infinity",
|
|
"location",
|
|
"lockdiscovery",
|
|
"lockentry",
|
|
"lockinfo",
|
|
"lockroot",
|
|
"lockscope",
|
|
"locktoken",
|
|
"lock-token-matches-request-uri",
|
|
"lock-token-submitted",
|
|
"locktype",
|
|
"multistatus",
|
|
"no-conflicting-lock",
|
|
"no-external-entities",
|
|
"owner",
|
|
"preserved-live-properties",
|
|
"prop",
|
|
"propertyupdate",
|
|
"propfind",
|
|
"propfind-finite-depth",
|
|
"propname",
|
|
"propstat",
|
|
"remove",
|
|
"resourcetype",
|
|
"response",
|
|
"responsedescription",
|
|
"set",
|
|
"shared",
|
|
"status",
|
|
"supportedlock",
|
|
"text/html",
|
|
"timeout",
|
|
"write",
|
|
];
|
|
|
|
#[derive(Arbitrary)]
|
|
enum Token {
|
|
Known(usize),
|
|
//Unknown(String),
|
|
}
|
|
impl Token {
|
|
fn serialize(&self) -> String {
|
|
match self {
|
|
Self::Known(i) => tokens[i % tokens.len()].to_string(),
|
|
//Self::Unknown(v) => v.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Arbitrary)]
|
|
struct Tag {
|
|
//prefix: Option<Token>,
|
|
name: Token,
|
|
attr: Option<(Token, Token)>,
|
|
}
|
|
impl Tag {
|
|
fn start(&self) -> String {
|
|
let mut acc = String::new();
|
|
/*if let Some(p) = &self.prefix {
|
|
acc.push_str(p.serialize().as_str());
|
|
acc.push_str(":");
|
|
}*/
|
|
acc.push_str("D:");
|
|
acc.push_str(self.name.serialize().as_str());
|
|
|
|
if let Some((k, v)) = &self.attr {
|
|
acc.push_str(" ");
|
|
acc.push_str(k.serialize().as_str());
|
|
acc.push_str("=\"");
|
|
acc.push_str(v.serialize().as_str());
|
|
acc.push_str("\"");
|
|
}
|
|
acc
|
|
}
|
|
fn end(&self) -> String {
|
|
let mut acc = String::new();
|
|
acc.push_str("D:");
|
|
acc.push_str(self.name.serialize().as_str());
|
|
acc
|
|
}
|
|
}
|
|
|
|
#[derive(Arbitrary)]
|
|
enum XmlNode {
|
|
//@FIXME: build RFC3339 and RFC822 Dates with chrono based on timestamps
|
|
//@FIXME: add small numbers
|
|
//@FIXME: add http status code
|
|
Node(Tag, Vec<Self>),
|
|
Number(u64),
|
|
Text(Token),
|
|
}
|
|
impl std::fmt::Debug for XmlNode {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}", self.serialize())
|
|
}
|
|
}
|
|
impl XmlNode {
|
|
fn serialize(&self) -> String {
|
|
match self {
|
|
Self::Node(tag, children) => {
|
|
let stag = tag.start();
|
|
match children.is_empty() {
|
|
true => format!("<{}/>", stag),
|
|
false => format!(
|
|
"<{}>{}</{}>",
|
|
stag,
|
|
children.iter().map(|v| v.serialize()).collect::<String>(),
|
|
tag.end()
|
|
),
|
|
}
|
|
}
|
|
Self::Number(v) => format!("{}", v),
|
|
Self::Text(v) => v.serialize(),
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn serialize(elem: &impl xml::QWrite) -> Vec<u8> {
|
|
let mut buffer = Vec::new();
|
|
let mut tokio_buffer = tokio::io::BufWriter::new(&mut buffer);
|
|
let q = quick_xml::writer::Writer::new_with_indent(&mut tokio_buffer, b' ', 4);
|
|
let ns_to_apply = vec![("xmlns:D".into(), "DAV:".into())];
|
|
let mut writer = xml::Writer { q, ns_to_apply };
|
|
|
|
elem.qwrite(&mut writer).await.expect("xml serialization");
|
|
tokio_buffer.flush().await.expect("tokio buffer flush");
|
|
|
|
return buffer;
|
|
}
|
|
|
|
type Object = types::Multistatus<realization::Core, types::PropValue<realization::Core>>;
|
|
|
|
fuzz_target!(|nodes: XmlNode| {
|
|
let gen = format!(
|
|
"<D:multistatus xmlns:D=\"DAV:\">{}<D:/multistatus>",
|
|
nodes.serialize()
|
|
);
|
|
//println!("--------\n{}", gen);
|
|
let data = gen.as_bytes();
|
|
|
|
let rt = Runtime::new().expect("tokio runtime initialization");
|
|
|
|
rt.block_on(async {
|
|
// 1. Setup fuzzing by finding an input that seems correct, do not crash yet then.
|
|
let mut rdr = match xml::Reader::new(NsReader::from_reader(data)).await {
|
|
Err(_) => return,
|
|
Ok(r) => r,
|
|
};
|
|
let reference = match rdr.find::<Object>().await {
|
|
Err(_) => return,
|
|
Ok(m) => m,
|
|
};
|
|
|
|
// 2. Re-serialize the input
|
|
let my_serialization = serialize(&reference).await;
|
|
|
|
// 3. De-serialize my serialization
|
|
let mut rdr2 = xml::Reader::new(NsReader::from_reader(my_serialization.as_slice()))
|
|
.await
|
|
.expect("XML Reader init");
|
|
let comparison = rdr2.find::<Object>().await.expect("Deserialize again");
|
|
|
|
// 4. Both the first decoding and last decoding must be identical
|
|
assert_eq!(reference, comparison);
|
|
})
|
|
});
|