From 5307ec3ef566f7f1e9706759bc67fc89025298e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Thu, 11 Mar 2021 22:13:43 +0100 Subject: [PATCH 1/2] serializer: Add a TODO for something the fuzzer caught which was a real bug (sorta) --- src/serializer.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/serializer.rs b/src/serializer.rs index abd50c3d..9cde6dc8 100644 --- a/src/serializer.rs +++ b/src/serializer.rs @@ -107,6 +107,8 @@ impl<'a> ToCss for Token<'a> { write_numeric(value, int_value, has_sign, dest)?; // Disambiguate with scientific notation. let unit = &**unit; + // TODO(emilio): This doesn't handle e.g. 100E1m, which gets us + // an unit of "E1m"... if unit == "e" || unit == "E" || unit.starts_with("e-") || unit.starts_with("E-") { dest.write_str("\\65 ")?; serialize_name(&unit[1..], dest)?; From d2dc268bb0be87dfda4ec8b9c72684257d0a1a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Thu, 11 Mar 2021 22:17:23 +0100 Subject: [PATCH 2/2] Add fuzzing support. --- fuzz/.gitignore | 4 + fuzz/Cargo.lock | 266 +++++++++++++++++++++++++++++++++ fuzz/Cargo.toml | 26 ++++ fuzz/fuzz_targets/cssparser.rs | 96 ++++++++++++ 4 files changed, 392 insertions(+) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.lock create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/fuzz_targets/cssparser.rs diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 00000000..572e03bd --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ + +target +corpus +artifacts diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock new file mode 100644 index 00000000..da45b9a2 --- /dev/null +++ b/fuzz/Cargo.lock @@ -0,0 +1,266 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arbitrary" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "698b65a961a9d730fb45b6b0327e20207810c9f61ee421b082b27ba003f49e2b" + +[[package]] +name = "cc" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cssparser" +version = "0.28.1" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf", + "proc-macro2", + "quote", + "smallvec", + "syn", +] + +[[package]] +name = "cssparser-fuzz" +version = "0.0.0" +dependencies = [ + "cssparser", + "libfuzzer-sys", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.0" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "dtoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e" + +[[package]] +name = "dtoa-short" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" +dependencies = [ + "dtoa", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "libc" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86c975d637bc2a2f99440932b731491fc34c7f785d239e38af3addd3c2fd0e46" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros", + "phf_shared", + "proc-macro-hack", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core", +] + +[[package]] +name = "siphasher" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "syn" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd9bc7ccc2688b3344c2f48b9b546648b25ce0b20fc717ee7fa7981a8ca9717" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 00000000..ebe2036e --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,26 @@ + +[package] +name = "cssparser-fuzz" +version = "0.0.0" +authors = ["Automatically generated"] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.cssparser] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "cssparser" +path = "fuzz_targets/cssparser.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/cssparser.rs b/fuzz/fuzz_targets/cssparser.rs new file mode 100644 index 00000000..6299a14d --- /dev/null +++ b/fuzz/fuzz_targets/cssparser.rs @@ -0,0 +1,96 @@ +#![no_main] + +use cssparser::*; + +const DEBUG: bool = false; + +fn parse_and_serialize(input: &str, preserving_comments: bool) -> String { + let mut input = ParserInput::new(input); + let mut parser = Parser::new(&mut input); + let mut serialization = String::new(); + let result = do_parse_and_serialize( + &mut parser, + preserving_comments, + TokenSerializationType::nothing(), + &mut serialization, + 0, + ); + if result.is_err() { + return String::new(); + } + serialization +} + +fn do_parse_and_serialize<'i>( + input: &mut Parser<'i, '_>, + preserving_comments: bool, + mut previous_token_type: TokenSerializationType, + serialization: &mut String, + indent_level: usize, +) -> Result<(), ParseError<'i, ()>> { + loop { + let token = if preserving_comments { + input.next_including_whitespace_and_comments() + } else { + input.next_including_whitespace() + }; + let token = match token { + Ok(token) => token, + Err(..) => break, + }; + if DEBUG { + for _ in 0..indent_level { + print!(" "); + } + println!("{:?}", token); + } + if token.is_parse_error() { + let token = token.clone(); + return Err(input.new_unexpected_token_error(token)) + } + let token_type = token.serialization_type(); + if previous_token_type.needs_separator_when_before(token_type) { + serialization.push_str("/**/"); + } + previous_token_type = token_type; + token.to_css(serialization).unwrap(); + let closing_token = match token { + Token::Function(_) | Token::ParenthesisBlock => Token::CloseParenthesis, + Token::SquareBracketBlock => Token::CloseSquareBracket, + Token::CurlyBracketBlock => Token::CloseCurlyBracket, + _ => continue, + }; + + input.parse_nested_block(|input| -> Result<_, ParseError<()>> { + do_parse_and_serialize(input, preserving_comments, previous_token_type, serialization, indent_level + 1) + })?; + + closing_token.to_css(serialization).unwrap(); + } + Ok(()) +} + +fn fuzz(data: &str, preserving_comments: bool) { + let serialization = parse_and_serialize(data, preserving_comments); + let reserialization = parse_and_serialize(&serialization, preserving_comments); + if DEBUG { + println!("IN: {:?}", serialization); + println!("OUT: {:?}", reserialization); + } + // TODO: This should ideally pass, but it doesn't for numbers near our + // precision limits, so parsing e.g., 9999995e-45 generates a serialization + // of 10e-39 because of dtoa rounding, and parsing _that_ generates a + // serialization of 1e-38. + // + // assert_eq!( + // serialization, reserialization, + // "Serialization should be idempotent" + // ); +} + +libfuzzer_sys::fuzz_target!(|data: &str| { + fuzz(data, false); + // TODO(emilio): Serialization when preserving comments is not idempotent. + // But in browsers we never preserve comments so that's ok... + // fuzz(data, true); +});