diff options
Diffstat (limited to 'aero-dav/fuzz/fuzz_targets')
-rw-r--r-- | aero-dav/fuzz/fuzz_targets/dav.rs | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/aero-dav/fuzz/fuzz_targets/dav.rs b/aero-dav/fuzz/fuzz_targets/dav.rs new file mode 100644 index 0000000..a303401 --- /dev/null +++ b/aero-dav/fuzz/fuzz_targets/dav.rs @@ -0,0 +1,209 @@ +#![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); + }) +}); |