diff --git a/fuzz-libafl/.gitignore b/fuzz-libafl/.gitignore new file mode 100644 index 00000000..f0bbe8ed --- /dev/null +++ b/fuzz-libafl/.gitignore @@ -0,0 +1,5 @@ +crash-artifacts +crashes +target +*.json +.cur_input* \ No newline at end of file diff --git a/fuzz-libafl/Cargo.lock b/fuzz-libafl/Cargo.lock new file mode 100644 index 00000000..2c28ea81 --- /dev/null +++ b/fuzz-libafl/Cargo.lock @@ -0,0 +1,1688 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arbitrary-int" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825297538d77367557b912770ca3083f778a196054b3ee63b22673c4a3cae0a5" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link 0.2.1", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitbybit" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb157f9753a7cddfcf4a4f5fed928fbf4ce1b7b64b6bcc121d7a9f95d698997b" +dependencies = [ + "arbitrary-int", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bitmask-enum" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6cbbb8f56245b5a479b30a62cdc86d26e2f35c2b9f594bc4671654b03851380" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "const_panic" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e262cdaac42494e3ae34c43969f9cdeb7da178bdb4b66fa6a1ea2edb4c8ae652" +dependencies = [ + "typewit", +] + +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "ctor" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec09e802f5081de6157da9a75701d6c713d8dc3ba52571fd4bd25f412644e8a6" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dtor" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97cbdf2ad6846025e8e25df05171abfb30e3ababa12ee0a0e44b9bbe570633a8" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "enum-field-getter" +version = "0.1.0" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "fastbloom" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee0191af12b622a9263467105cda1a21f8e6cbc535b2fd49de829ca92794e32" +dependencies = [ + "getrandom 0.3.4", + "siphasher", + "wide", +] + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hashers" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bca93b15ea5a746f220e56587f71e73c6165eab783df9e26590069953e3c30" +dependencies = [ + "fxhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hostname" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +dependencies = [ + "cfg-if", + "libc", + "windows-link 0.2.1", +] + +[[package]] +name = "hyperquark" +version = "0.0.11" +dependencies = [ + "bitmask-enum", + "convert_case", + "enum-field-getter", + "hashers", + "indexmap", + "itertools 0.14.0", + "lazy-regex", + "petgraph", + "regex", + "sb3", + "serde", + "serde-wasm-bindgen", + "serde_json", + "uuid", + "wasm-bindgen", + "wasm-encoder 0.245.1", + "wasm-gen", +] + +[[package]] +name = "hyperquark-libafl-fuzz" +version = "0.1.0" +dependencies = [ + "arbitrary", + "hyperquark", + "itertools 0.15.0", + "libafl", + "libafl_bolts", + "mutatis", + "sb3", + "serde", + "serde_json", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy-regex" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bae91019476d3ec7147de9aa291cadb6d870abf2f3015d2da73a90325ac1496" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de9c1e1439d8b7b3061b2d209809f447ca33241733d9a3c01eabf2dc8d94358" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.117", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libafl" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07dd8d2c0eadc478a759f6d3862eec49a6818a0316b415bb0d2638146b021738" +dependencies = [ + "ahash", + "arbitrary-int", + "backtrace", + "bincode", + "bitbybit", + "const_format", + "const_panic", + "fastbloom", + "fs2", + "hashbrown 0.14.5", + "libafl_bolts", + "libafl_derive", + "libc", + "libm", + "log", + "meminterval", + "nix", + "num-traits", + "postcard", + "regex", + "rustversion", + "serde", + "serde_json", + "serial_test", + "tuple_list", + "typed-builder", + "uuid", + "wait-timeout", + "winapi", + "windows", +] + +[[package]] +name = "libafl_bolts" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35162166ed6c0193089e6c23de536ef66c1644d987c1e590a581bd47f7988bb6" +dependencies = [ + "ahash", + "backtrace", + "ctor", + "erased-serde", + "hashbrown 0.14.5", + "hostname", + "libafl_derive", + "libafl_wide", + "libc", + "log", + "mach2", + "miniz_oxide", + "nix", + "num_enum", + "once_cell", + "postcard", + "rand_core 0.9.5", + "rustversion", + "serde", + "serial_test", + "static_assertions", + "tuple_list", + "typeid", + "uds", + "uuid", + "winapi", + "windows", + "windows-result", + "xxhash-rust", +] + +[[package]] +name = "libafl_derive" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61adf76899bffdcd15ae7fea42b978e7df7cf9213aacdd8cdcda89e4bb3bc32d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "libafl_wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f28d525f6e361b6cd55c0da5347027860a902d638d15194c16dc2f39a5ba9f" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "meminterval" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e0f9a537564310a87dc77d5c88a407e27dd0aa740e070f0549439cfcc68fcfd" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mutatis" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "468ca2a8bc8087a0b2c11d21e7dca51d92cc2662a6993d6b56bac2701795f202" +dependencies = [ + "mutatis-derive", + "rand", +] + +[[package]] +name = "mutatis-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5708f6e65277d4a89733aff227b43d0989c5ead03999b6f1f33a8ec7a9978361" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", + "serde", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "sb3" +version = "0.1.0" +dependencies = [ + "arbitrary", + "enum-field-getter", + "libafl", + "libafl_bolts", + "mutatis", + "serde", + "serde_json", +] + +[[package]] +name = "scc" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +dependencies = [ + "sdd", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdd" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serial_test" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f" +dependencies = [ + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tuple_list" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "141fb9f71ee586d956d7d6e4d5a9ef8e946061188520140f7591b668841d502e" + +[[package]] +name = "typed-builder" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef81aec2ca29576f9f6ae8755108640d0a86dd3161b2e8bca6cfa554e98f77d" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecb9ecf7799210407c14a8cfdfe0173365780968dc57973ed082211958e0b18" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typewit" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06fee3a8df48c50c55ad646a4e03b00a370da6fe1850ebf467a8d0165dfcafae" + +[[package]] +name = "uds" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "885c31f06fce836457fe3ef09a59f83fe8db95d270b11cd78f40a4666c4d1661" +dependencies = [ + "libc", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9dca005e69bf015e45577e415b9af8c67e8ee3c0e38b5b0add5aa92581ed5c" +dependencies = [ + "leb128fmt", + "wasmparser 0.245.1", +] + +[[package]] +name = "wasm-gen" +version = "0.2.0" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder 0.244.0", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wasmparser" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" +dependencies = [ + "bitflags", + "indexmap", + "semver", +] + +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.244.0", + "wasm-metadata", + "wasmparser 0.244.0", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.244.0", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/fuzz-libafl/Cargo.toml b/fuzz-libafl/Cargo.toml new file mode 100644 index 00000000..b207d2c9 --- /dev/null +++ b/fuzz-libafl/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "hyperquark-libafl-fuzz" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +arbitrary = "1.4.2" +hyperquark = { path = ".." } +itertools = "0.15.0" +libafl = { version = "=0.15.3", features = ["prelude"] } +libafl_bolts = "=0.15.3" +mutatis = "0.5.3" +sb3 = { path = "../sb3", features = ["fuzz"] } +serde = "1.0.228" +serde_json = "1.0" diff --git a/fuzz-libafl/README.md b/fuzz-libafl/README.md new file mode 100644 index 00000000..0d4393b2 --- /dev/null +++ b/fuzz-libafl/README.md @@ -0,0 +1,68 @@ +# HyperQuark libAFL fuzzer + +This crate fuzzes crashes in `hyperquark` by: + +1. Generating `sb3::structured::StructuredProject` values from arbitrary bytes. +2. Converting to `sb3::raw::Sb3Project`. +3. Serializing to JSON. +4. Calling `hyperquark::sb3_to_wasm`. + +Crashes found by libAFL are written to `./crashes`. +Recovered crash artifacts are written to `./crash-artifacts` as matching `.json` and `.txt` files. +Artifacts use the same basename as the crash file, for example: + +- `crashes/aabbccddeeff0011` +- `crash-artifacts/aabbccddeeff0011.json` +- `crash-artifacts/aabbccddeeff0011.txt` + +Initial seeds can be placed in `./corpus` as raw byte files. +The harness will load them automatically at startup. + +## Run + +```sh +cd fuzz-libafl +cargo run --release +``` + +## Recover SB3 From Crash File + +Crash files in `./crashes` are raw byte inputs. To recover the generated SB3 JSON: + +```sh +cd fuzz-libafl +cargo run --release --bin recover_sb3 -- crashes/ recovered-project.json +``` + +If you omit `recovered-project.json`, the JSON is printed to stdout. + +## Replay Crash With Backtrace + +To replay a crash and print a Rust backtrace in one command: + +```sh +cd fuzz-libafl +cargo run --release --bin replay_crash -- crashes/ recovered-project.json +``` + +This command also writes the recovered `project.json` if an output path is provided. +If the panic is reproduced, the process exits with a non-zero status. + +## Reduce A Scratch Project + +To shrink a `project.json` while keeping another command's output identical: + +```sh +cd fuzz-libafl +cargo run --release --bin reduce_project -- -- [args...] --input project.json --output reduced.json +``` + +The reducer mutates the `sb3::structured::StructuredProject` directly, serializes each candidate back to JSON, and keeps only candidates that are smaller and produce the same command output as the baseline run. + +## Notes + +- Inputs that fail `StructuredProject` generation or conversion are skipped. +- The fuzzer uses execution time feedback and crash objective (panic/unrecoverable faults). +- If no seed is accepted during startup, the harness inserts fallback byte seeds so fuzzing can begin. +- The runner backfills artifacts for existing crash files at startup and continues watching for new crash files. +- If the process aborts before any crash file is written, there is no input to recover. diff --git a/fuzz-libafl/scripts/run-project-script.mjs b/fuzz-libafl/scripts/run-project-script.mjs new file mode 100755 index 00000000..5714f0c7 --- /dev/null +++ b/fuzz-libafl/scripts/run-project-script.mjs @@ -0,0 +1,39 @@ +#!/usr/bin/env node + +import { run } from "../../playground/dist/run-project.mjs"; +import { argv } from "node:process"; +import { readFile } from "node:fs/promises"; + +console.log(argv); + +let opts = {}; + +let i = 4; + +let this_opt = null; +while (i < argv.length) { + const arg = argv[i]; + if (/^--[\w_]+$/.test(arg)) { + if (this_opt === null) { + this_opt = arg.substring(2); + } else { + throw new Error(`Unexpected argument ${arg}`); + } + } else { + if (this_opt !== null) { + if (this_opt === "unroll_loops") { + opts[this_opt] = parseFloat(arg); + } else { + opts[this_opt] = arg; + } + this_opt = null; + } else { + throw new Error(`Unexpected argument ${arg}`); + } + } + i += 1; +} + +console.log(opts) + +await run(JSON.parse(await readFile(argv[3]), "utf-8"), opts); diff --git a/fuzz-libafl/scripts/run-project.mjs b/fuzz-libafl/scripts/run-project.mjs new file mode 100644 index 00000000..0f3918c1 --- /dev/null +++ b/fuzz-libafl/scripts/run-project.mjs @@ -0,0 +1,16 @@ +import { makeTestRunner } from "../../test/integration/test-run-project.mjs"; +import { defaultSettings } from "../../playground/lib/settings.js"; + +export async function run(project_json, opts = {}) { + let settings = Object.assign(defaultSettings.to_js(), opts); + + const runner = await makeTestRunner(project_json, {}, settings); + + runner.addEventListener("timeout", () => { + throw new Error(`Timeout waiting for threads to complete: ${uri}`); + }); + + runner.flag_clicked(); + + await runner.run(); +} diff --git a/fuzz-libafl/scripts/run-project.sh b/fuzz-libafl/scripts/run-project.sh new file mode 100755 index 00000000..f5639b7e --- /dev/null +++ b/fuzz-libafl/scripts/run-project.sh @@ -0,0 +1,8 @@ +#!/bin/bash +if [[ $BASH_SOURCE = */* ]]; then + thisdir=${BASH_SOURCE%/*}/ +else + thisdir=./ +fi +# "${thisdir}/run-project-script.mjs" -- "$1" --unroll_loops 0 | grep "project stopped" +"${thisdir}/run-project-script.mjs" -- "$1" #2>&1 >/dev/null | grep Error \ No newline at end of file diff --git a/fuzz-libafl/src/bin/recover_sb3.rs b/fuzz-libafl/src/bin/recover_sb3.rs new file mode 100644 index 00000000..a9ef2401 --- /dev/null +++ b/fuzz-libafl/src/bin/recover_sb3.rs @@ -0,0 +1,39 @@ +use arbitrary::Arbitrary; +use std::path::PathBuf; + +fn usage(bin_name: &str) { + eprintln!("Usage: {bin_name} [output-project-json]"); +} + +fn main() -> Result<(), Box> { + let mut args = std::env::args(); + let bin_name = args.next().unwrap_or_else(|| "recover_sb3".into()); + + let Some(crash_file) = args.next() else { + usage(&bin_name); + return Err("missing crash-file argument".into()); + }; + + let output_path = args.next().map(PathBuf::from); + + let data = std::fs::read(&crash_file)?; + let mut unstructured = arbitrary::Unstructured::new(&data); + + let structured = sb3::structured::StructuredProject::arbitrary(&mut unstructured) + .map_err(|_| "failed to decode crash bytes as StructuredProject")?; + + let raw_project: sb3::raw::Sb3Project = structured + .try_into() + .map_err(|_| "decoded structure could not be converted to raw sb3")?; + + let json = serde_json::to_string_pretty(&raw_project)?; + + if let Some(path) = output_path { + std::fs::write(&path, json.as_bytes())?; + println!("Recovered SB3 JSON written to {}", path.display()); + } else { + println!("{json}"); + } + + Ok(()) +} diff --git a/fuzz-libafl/src/bin/reduce_project.rs b/fuzz-libafl/src/bin/reduce_project.rs new file mode 100644 index 00000000..afbaa568 --- /dev/null +++ b/fuzz-libafl/src/bin/reduce_project.rs @@ -0,0 +1,465 @@ +use std::borrow::Cow; +use std::fs::OpenOptions; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::sync::{Arc, Mutex}; + +use itertools::Itertools; +use libafl::executors::command::StdCommandConfigurator; +use libafl::prelude::*; +use libafl_bolts::prelude::*; +use mutatis::Session; +use sb3::StructuredProject; + +#[derive(Clone, Debug, PartialEq, Eq)] +struct CommandOutcome { + status: Option, + stdout: Vec, + stderr: Vec, +} + +#[derive(Debug)] +struct Config { + command: Vec, + input_path: PathBuf, + output_path: Option, + max_rounds: usize, +} + +fn usage(bin_name: &str) { + eprintln!( + "Usage: {bin_name} -- [args...] --input [--output \ + ] [--max-rounds N]" + ); +} + +fn parse_args() -> Result { + let mut args = std::env::args().skip(1); + match args.next().as_deref() { + Some("--") => {} + _ => return Err("expected `--` before the command".into()), + } + + let mut command = Vec::new(); + let mut input_path = None; + let mut output_path = None; + let mut max_rounds = 1024usize; + + while let Some(arg) = args.next() { + match arg.as_str() { + "--input" => { + let Some(path) = args.next() else { + return Err("missing value for --input".into()); + }; + input_path = Some(PathBuf::from(path)); + } + "--output" => { + let Some(path) = args.next() else { + return Err("missing value for --output".into()); + }; + output_path = Some(PathBuf::from(path)) + } + "--max-rounds" => { + let Some(value) = args.next() else { + return Err("missing value for --max-rounds".into()); + }; + max_rounds = value + .parse::() + .map_err(|_| "--max-rounds must be a number".to_string())?; + } + other => command.push(other.to_string()), + } + } + + if command.is_empty() { + return Err("missing command to execute".into()); + } + + let Some(input_path) = input_path else { + return Err("missing --input ".into()); + }; + + Ok(Config { + command, + input_path, + output_path, + max_rounds, + }) +} + +fn read_project( + path: &Path, +) -> Result> { + let json = std::fs::read_to_string(path)?; + let raw = sb3::raw::Sb3Project::try_from(json.as_str())?; + Ok(sb3::structured::StructuredProject::try_from(raw)?) +} + +fn project_json(project: &sb3::structured::StructuredProject) -> Option { + let raw = sb3::raw::Sb3Project::try_from(project.clone()).ok()?; + serde_json::to_string(&raw).ok() +} + +fn write_temp_project_json( + project: &sb3::structured::StructuredProject, +) -> Result> { + let Some(json) = project_json(project) else { + return Err("failed to serialize baseline project".into()); + }; + + let mut path = std::env::temp_dir(); + path.push(format!( + "hyperquark-reduce-project-{}-{}.json", + std::process::id(), + current_nanos() + )); + + let mut file = OpenOptions::new() + .write(true) + .create_new(true) + .open(&path)?; + file.write_all(json.as_bytes())?; + + Ok(path) +} + +fn capture_baseline( + command: &[String], + project: &sb3::structured::StructuredProject, +) -> Result> { + let temp_path = write_temp_project_json(project)?; + // let Some(json) = project_json(project) else { + // return Err("failed to serialize baseline project".into()); + // }; + + let mut child = Command::new(&command[0]); + child.args(&command[1..]); + child.arg(dbg!(&temp_path)); + // child.arg(&json); + + println!("{command:?}"); + + let output = child.output()?; + let outcome = CommandOutcome { + status: output.status.code(), + stdout: output.stdout, + stderr: output.stderr, + }; + + Ok(outcome) +} + +#[derive(Clone)] +struct CommandObserver<'a> { + stdout: Handle, + stderr: Handle, + baseline: &'a CommandOutcome, + // best_candidate: Arc>>, +} + +impl<'a> CommandObserver<'a> { + fn new( + stdout: Handle, + stderr: Handle, + baseline: &'a CommandOutcome, + ) -> Self { + Self { + stdout, + stderr, + baseline, + // best_candidate: Arc::new(Mutex::new(None)), + } + } + + // fn best_candidate(&self) -> Option<(usize, StructuredProject)> { + // self.best_candidate.lock().ok()?.clone() + // } + + fn stdout(&self) -> &Handle { + &self.stdout + } + + fn stderr(&self) -> &Handle { + &self.stderr + } +} + +impl<'a> Named for CommandObserver<'a> { + fn name(&self) -> &Cow<'static, str> { + &Cow::Borrowed("CommandObserver") + } +} + +impl<'a, S> StateInitializer for CommandObserver<'a> {} + +impl<'a, EM, OT, S> Feedback for CommandObserver<'a> +where + OT: MatchNameRef, +{ + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + input: &StructuredProject, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result { + let stdout = observers.get(&self.stdout).unwrap().output.clone(); + let stderr = observers.get(&self.stderr).unwrap().output.clone(); + // println!("{:?}", stdout.as_ref().cloned().map(String::from_utf8)); + // println!("{:?}", stderr.as_ref().cloned().map(String::from_utf8)); + // println!("{:?}", String::from_utf8(self.baseline.stdout.clone())); + // println!("{:?}", String::from_utf8(self.baseline.stderr.clone())); + // println!("{}", input.len()); + let interesting = stdout.clone().unwrap_or_default() == self.baseline.stdout + && stderr.clone().unwrap_or_default() == self.baseline.stderr; + let input_len = input.len(); + + // println!("{input_len}"); + // println!("{interesting}"); + Ok(interesting) + + // Ok(interesting) + } +} + +impl<'a, T> FeedbackFactory, T> for CommandObserver<'a> { + fn create_feedback(&self, _ctx: &T) -> CommandObserver<'a> { + self.clone() + } +} + +type ScratchCommandState = StdState< + InMemoryCorpus, + StructuredProject, + RomuDuoJrRand, + InMemoryCorpus, +>; +type ScratchCommandObservers = tuple_list!(StdOutObserver, StdErrObserver); +type ScratchCommandFuzzer<'a> = + StdFuzzer; + +struct ScratchCommandExecutor { + cmd_executor: CommandExecutor< + StructuredProject, + ScratchCommandObservers, + ScratchCommandState, + StdCommandConfigurator, + >, +} + +impl ScratchCommandExecutor { + fn new( + command: &[String], + cmd_observer: &CommandObserver, + observers: ScratchCommandObservers, + ) -> Self { + let ex = CommandExecutor::builder() + .program(command.first().unwrap()) + .args(command.iter().dropping(1).collect::>()) + .arg_input_file(format!( + "{}/hyperquark-reduce-project-{}-{}.json", + std::env::temp_dir().display(), + std::process::id(), + current_nanos() + )) + .stdout_observer(cmd_observer.stdout().clone()) + .stderr_observer(cmd_observer.stderr().clone()) + .build(observers) + .unwrap(); + Self { cmd_executor: ex } + } +} + +impl HasObservers for ScratchCommandExecutor { + type Observers = ScratchCommandObservers; + + fn observers(&self) -> RefIndexable<&Self::Observers, Self::Observers> { + self.cmd_executor.observers() + } + + fn observers_mut(&mut self) -> RefIndexable<&mut Self::Observers, Self::Observers> { + self.cmd_executor.observers_mut() + } +} + +impl Executor> + for ScratchCommandExecutor +{ + fn run_target( + &mut self, + fuzzer: &mut ScratchCommandFuzzer, + state: &mut ScratchCommandState, + mgr: &mut EM, + input: &StructuredProject, + ) -> Result { + self.cmd_executor.run_target(fuzzer, state, mgr, input) + } +} + +struct StructuredProjectMutator; + +impl Named for StructuredProjectMutator { + fn name(&self) -> &Cow<'static, str> { + &Cow::Borrowed("StructuredProjectMutator") + } +} + +impl Mutator for StructuredProjectMutator +where + S: HasRand, +{ + fn mutate( + &mut self, + state: &mut S, + input: &mut StructuredProject, + ) -> Result { + let seed = state.rand_mut().next(); + + // println!("{}", input.len()); + + let mut session = Session::new().shrink(true).seed(seed); + + session.mutate(input).unwrap(); + + // println!("mutated: {}", input.len()); + + Ok(MutationResult::Mutated) + } + + fn post_exec(&mut self, _state: &mut S, _new_corpus_id: Option) -> Result<(), Error> { + Ok(()) + } +} + +fn main() -> Result<(), Box> { + let mut args = std::env::args(); + let bin_name = args.next().unwrap_or_else(|| "reduce_project".into()); + let config = match parse_args() { + Ok(config) => config, + Err(message) => { + usage(&bin_name); + return Err(message.into()); + } + }; + + let base_case = read_project(&config.input_path)?; + let baseline = capture_baseline(&config.command, &base_case)?; + + println!( + "baseline captured: status={:?}, stdout={} bytes, stderr={} bytes", + baseline.status, + baseline.stdout.len(), + baseline.stderr.len() + ); + println!("{}", String::from_utf8(baseline.stdout.clone()).unwrap()); + println!("{}", String::from_utf8(baseline.stderr.clone()).unwrap()); + + let stdout = StdOutObserver::new("stdout".into())?; + let stderr = StdErrObserver::new("stderr".into())?; + + let mut cmd_observer = CommandObserver::new(stdout.handle(), stderr.handle(), &baseline); + + let observers = tuple_list!(stdout, stderr); + + let mut executor = ScratchCommandExecutor::new(&config.command, &cmd_observer, observers); + + let monitor = SimpleMonitor::new(|msg| println!("{msg}")); + let mut event_manager = SimpleEventManager::new(monitor); + + // let mutator = StructuredProjectMutator; + + // let stage: StdTMinMutationalStage = + // StdTMinMutationalStage::new(mutator, cmd_observer.clone(), config.max_rounds); + + let mut corpus = InMemoryCorpus::::new(); + corpus.add(Testcase::new(base_case.clone()))?; + + let mut state = StdState::new( + StdRand::with_seed(current_nanos()), + corpus, + InMemoryCorpus::::new(), + &mut (), + &mut (), + )?; + + let scheduler = QueueScheduler::new(); + let mut fuzzer: ScratchCommandFuzzer = StdFuzzer::new(scheduler, (), ()); + + // state.generate_initial_inputs_forced( + // &mut fuzzer, + // &mut executor, + // SingletonGenerator(base_case), + // &mut event_manager, + // 1, + // )?; + + // stage.perform(&mut fuzzer, &mut executor, &mut state, &mut event_manager)?; + + // fuzzer.fuzz_one( + // &mut tuple_list!(stage), + // &mut executor, + // &mut state, + // &mut event_manager, + // )?; + + let mut i = 0; + let mut input = base_case; + let mut best_len = input.len(); + let mut session = Session::new().shrink(true); + let output_path: PathBuf = config.output_path.unwrap_or(config.input_path); + + loop { + if i == config.max_rounds { + break; + } + + i += 1; + + let mut this_input = input.clone(); + + session.mutate(&mut this_input).unwrap(); + + if this_input.len() >= best_len { + continue; + } + + let exit_kind = + fuzzer.execute_input(&mut state, &mut executor, &mut event_manager, &this_input)?; + + let (_, corpus_id) = fuzzer.evaluate_execution( + &mut state, + &mut event_manager, + &this_input, + &*executor.observers(), + &exit_kind, + false, + )?; + + let interesting = cmd_observer.is_interesting( + &mut state, + &mut event_manager, + &this_input, + &*executor.observers(), + &exit_kind, + )?; + + if interesting { + i = 0; + input = this_input; + best_len = input.len(); + println!("new best: {best_len}"); + std::fs::write(output_path.clone(), project_json(&input).unwrap())?; + } + } + + // let Some((best_len, best_project)) = cmd_observer.best_candidate() else { + // return Err("tmin did not record a minimized candidate".into()); + // }; + + let best_json = project_json(&input).ok_or("failed to serialize minimized project")?; + println!("{best_len:?}"); + + Ok(()) +} diff --git a/fuzz-libafl/src/bin/replay_crash.rs b/fuzz-libafl/src/bin/replay_crash.rs new file mode 100644 index 00000000..b7992658 --- /dev/null +++ b/fuzz-libafl/src/bin/replay_crash.rs @@ -0,0 +1,70 @@ +use arbitrary::Arbitrary; +use hyperquark::wasm::flags::all_wasm_features; +use std::backtrace::Backtrace; +use std::path::PathBuf; + +fn usage(bin_name: &str) { + eprintln!("Usage: {bin_name} [output-project-json]"); +} + +fn panic_message(payload: &(dyn std::any::Any + Send)) -> &'static str { + if let Some(msg) = payload.downcast_ref::<&'static str>() { + msg + } else { + "" + } +} + +fn main() -> Result<(), Box> { + let mut args = std::env::args(); + let bin_name = args.next().unwrap_or_else(|| "replay_crash".into()); + + let Some(crash_file) = args.next() else { + usage(&bin_name); + return Err("missing crash-file argument".into()); + }; + + let output_path = args.next().map(PathBuf::from); + + let data = std::fs::read(&crash_file)?; + let mut unstructured = arbitrary::Unstructured::new(&data); + + let structured = sb3::structured::StructuredProject::arbitrary(&mut unstructured) + .map_err(|_| "failed to decode crash bytes as StructuredProject")?; + + let raw_project: sb3::raw::Sb3Project = structured + .try_into() + .map_err(|_| "decoded structure could not be converted to raw sb3")?; + + let json = serde_json::to_string_pretty(&raw_project)?; + + if let Some(path) = output_path { + std::fs::write(&path, json.as_bytes())?; + println!("Recovered SB3 JSON written to {}", path.display()); + } + + let flags = hyperquark::wasm::WasmFlags::new(all_wasm_features()); + + let replay = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let _ = hyperquark::sb3_to_wasm(&json, flags); + })); + + match replay { + Ok(()) => { + println!("No panic reproduced for this crash input."); + Ok(()) + } + Err(payload) => { + eprintln!("Panic reproduced while replaying {crash_file}"); + + if let Some(msg) = payload.downcast_ref::() { + eprintln!("panic message: {msg}"); + } else { + eprintln!("panic message: {}", panic_message(&*payload)); + } + + eprintln!("backtrace:\n{}", Backtrace::force_capture()); + std::process::exit(101); + } + } +} diff --git a/fuzz-libafl/src/main.rs b/fuzz-libafl/src/main.rs new file mode 100644 index 00000000..4fd42a5e --- /dev/null +++ b/fuzz-libafl/src/main.rs @@ -0,0 +1,233 @@ +use std::num::NonZeroUsize; +use std::path::PathBuf; +use std::time::Duration; +use std::collections::BTreeSet; + +use arbitrary::Arbitrary; +use hyperquark::error::HQErrorType; +use hyperquark::wasm::flags::all_wasm_features; +use libafl::Fuzzer; +use libafl::corpus::{Corpus, InMemoryCorpus, OnDiskCorpus, Testcase}; +use libafl::events::SimpleEventManager; +use libafl::executors::ExitKind; +use libafl::executors::inprocess::InProcessExecutor; +use libafl::feedbacks::{CrashFeedback, TimeFeedback}; +use libafl::fuzzer::StdFuzzer; +use libafl::generators::RandBytesGenerator; +use libafl::inputs::{BytesInput, HasTargetBytes}; +use libafl::monitors::SimpleMonitor; +use libafl::mutators::havoc_mutations; +use libafl::mutators::scheduled::HavocScheduledMutator; +use libafl::observers::TimeObserver; +use libafl::schedulers::QueueScheduler; +use libafl::stages::mutational::StdMutationalStage; +use libafl::state::{HasCorpus, StdState}; +use libafl_bolts::current_nanos; +use libafl_bolts::rands::StdRand; +use libafl_bolts::tuples::tuple_list; + +const INITIAL_INPUTS: usize = 64; +const MAX_INPUT_SIZE: usize = 16 * 1024; +const ARTIFACT_DIR: &str = "crash-artifacts"; + +fn recover_artifacts(data: &[u8]) -> Option<(String, String)> { + let mut unstructured = arbitrary::Unstructured::new(data); + + let structured = sb3::structured::StructuredProject::arbitrary(&mut unstructured).ok()?; + let raw_project: sb3::raw::Sb3Project = structured.clone().try_into().ok()?; + + let json = serde_json::to_string_pretty(&raw_project).ok()?; + let structured_txt = format!("{structured:#?}"); + + Some((json, structured_txt)) +} + +fn process_crash_file(crash_path: &PathBuf, artifact_dir: &PathBuf) { + let Some(stem) = crash_path.file_name().and_then(|name| name.to_str()) else { + return; + }; + + if stem.starts_with('.') || stem.ends_with(".metadata") { + return; + } + + let json_path = artifact_dir.join(format!("{stem}.json")); + let txt_path = artifact_dir.join(format!("{stem}.txt")); + + if json_path.exists() && txt_path.exists() { + return; + } + + let Ok(data) = std::fs::read(crash_path) else { + return; + }; + + let Some((json, structured_txt)) = recover_artifacts(&data) else { + return; + }; + + let _ = std::fs::write(json_path, json); + let _ = std::fs::write(txt_path, structured_txt); +} + +fn process_crash_directory(crash_dir: &PathBuf, artifact_dir: &PathBuf) { + let Ok(entries) = std::fs::read_dir(crash_dir) else { + return; + }; + + for entry in entries.flatten() { + let path = entry.path(); + if path.is_file() { + process_crash_file(&path, artifact_dir); + } + } +} + +fn crash_basenames(crash_dir: &PathBuf) -> BTreeSet { + let mut stems = BTreeSet::new(); + let Ok(entries) = std::fs::read_dir(crash_dir) else { + return stems; + }; + + for entry in entries.flatten() { + let path = entry.path(); + if !path.is_file() { + continue; + } + let Some(stem) = path.file_name().and_then(|name| name.to_str()) else { + continue; + }; + if stem.starts_with('.') || stem.ends_with(".metadata") { + continue; + } + stems.insert(stem.to_string()); + } + + stems +} + +fn compile_input(data: &[u8], flags: hyperquark::wasm::WasmFlags) { + let mut unstructured = arbitrary::Unstructured::new(data); + let Ok(structured) = sb3::structured::StructuredProject::arbitrary(&mut unstructured) else { + return; + }; + + let Ok(raw_project): Result = structured.try_into() else { + return; + }; + + let Ok(json) = serde_json::to_string(&raw_project) else { + return; + }; + + match hyperquark::sb3_to_wasm(&json, flags) { + Ok(_) => (), + Err(err) => match err.err_type { + HQErrorType::InternalError => { + panic!("{} at {}:{}:{}", err.msg, err.file, err.line, err.column) + } + _ => (), + }, + } +} + +fn main() -> Result<(), libafl::Error> { + std::fs::create_dir_all("corpus")?; + std::fs::create_dir_all("crashes")?; + std::fs::create_dir_all(ARTIFACT_DIR)?; + + let crash_dir = PathBuf::from("crashes"); + let artifact_dir = PathBuf::from(ARTIFACT_DIR); + + // Backfill artifacts for existing crash files from previous runs. + process_crash_directory(&crash_dir, &artifact_dir); + + // Keep generating artifacts as new crashes are written. + { + let watch_crash_dir = crash_dir.clone(); + let watch_artifact_dir = artifact_dir.clone(); + std::thread::spawn(move || { + let mut seen = crash_basenames(&watch_crash_dir); + loop { + process_crash_directory(&watch_crash_dir, &watch_artifact_dir); + + let current = crash_basenames(&watch_crash_dir); + for stem in current.difference(&seen) { + println!("New crash basename: {stem}"); + } + seen = current; + + std::thread::sleep(Duration::from_millis(500)); + } + }); + } + + let monitor = SimpleMonitor::new(|msg| println!("{msg}")); + let mut event_manager = SimpleEventManager::new(monitor); + + let time_observer = TimeObserver::new("exec_time"); + let mut feedback = TimeFeedback::new(&time_observer); + let mut objective = CrashFeedback::new(); + + let mut state = StdState::new( + StdRand::with_seed(current_nanos()), + InMemoryCorpus::::new(), + OnDiskCorpus::new("crashes")?, + &mut feedback, + &mut objective, + )?; + + let scheduler = QueueScheduler::new(); + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + let wasm_flags = hyperquark::wasm::WasmFlags::new(all_wasm_features()); + + let mut harness = move |input: &BytesInput| { + let bytes = input.target_bytes(); + compile_input(bytes.as_ref(), wasm_flags); + ExitKind::Ok + }; + + let mut executor = InProcessExecutor::new( + &mut harness, + tuple_list!(time_observer), + &mut fuzzer, + &mut state, + &mut event_manager, + )?; + + // Try to load user-provided corpus files first. + let seed_dirs = [PathBuf::from("corpus")]; + let _ = state.load_initial_inputs(&mut fuzzer, &mut executor, &mut event_manager, &seed_dirs); + + if state.must_load_initial_inputs() { + let mut generator = RandBytesGenerator::new(NonZeroUsize::new(MAX_INPUT_SIZE).unwrap()); + state.generate_initial_inputs( + &mut fuzzer, + &mut executor, + &mut generator, + &mut event_manager, + INITIAL_INPUTS, + )?; + } + + // Some feedback setups may reject all generated inputs; ensure corpus is never empty. + if state.corpus().count() == 0 { + let fallback_inputs = [ + Vec::new(), + vec![0_u8], + b"{}".to_vec(), + b"{\"targets\":[]}".to_vec(), + ]; + for data in fallback_inputs { + state + .corpus_mut() + .add(Testcase::new(BytesInput::new(data)))?; + } + } + + let mutator = HavocScheduledMutator::new(havoc_mutations()); + let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut event_manager) +} diff --git a/sb3/Cargo.toml b/sb3/Cargo.toml index cc79242e..1c4e5344 100644 --- a/sb3/Cargo.toml +++ b/sb3/Cargo.toml @@ -5,9 +5,16 @@ edition = "2024" publish = false [dependencies] +arbitrary = { version = "1.4.2", features = ["derive"] } enum-field-getter = { path = "../enum-field-getter" } +mutatis = { version = "0.5.3", features = ["derive", "alloc", "std"] } serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } serde_json = { version = "1.0", default-features = false, features = ["alloc"] } +libafl_bolts = { version = "=0.15.3", optional = true } +libafl = { version = "=0.15.3", optional = true } [dev-dependencies] -pretty_assertions = "1.4.1" \ No newline at end of file +pretty_assertions = "1.4.1" + +[features] +fuzz = ["libafl", "libafl_bolts"] \ No newline at end of file diff --git a/sb3/src/lib.rs b/sb3/src/lib.rs index fbee364c..7a907152 100644 --- a/sb3/src/lib.rs +++ b/sb3/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(macro_attr)] + pub mod raw; pub mod structured; diff --git a/sb3/src/structured.rs b/sb3/src/structured.rs index 774739ee..f221a691 100644 --- a/sb3/src/structured.rs +++ b/sb3/src/structured.rs @@ -1,4 +1,16 @@ +mod arbitrary; +mod mutate; + +use arbitrary::{Arbitrary, ArbitraryWithContext, TargetContext, Unstructured, arbitrary_context}; +#[cfg(feature = "fuzz")] +use libafl::inputs::{HasTargetBytes, Input}; +#[cfg(feature = "fuzz")] +use libafl_bolts::HasLen; +use mutatis::Mutate; use serde::{Deserialize, Serialize}; + +#[cfg(feature = "fuzz")] +use crate::raw::Sb3Project; use crate::raw::{Comment, Costume, Meta, Monitor, Sound, VarVal, VariableInfo}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -162,63 +174,66 @@ pub struct BroadcastId(pub usize); pub struct ProcedureId(pub usize); #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] +#[arbitrary_context] pub enum VariableRef { Global(GlobalVariableId), Local(LocalVariableId), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] +#[arbitrary_context] pub enum ListRef { Global(GlobalListId), Local(LocalListId), } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Arbitrary, Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Variable { pub scratch_id: Box, pub info: VariableInfo, } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Arbitrary, Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct List { pub scratch_id: Box, pub name: Box, pub value: Vec, } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Arbitrary, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub struct Broadcast { pub scratch_id: Box, pub name: Box, } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Arbitrary, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub struct Procedure { pub proccode: Box, pub arguments: Vec, pub warp: bool, } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Arbitrary, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub struct ProcedureArgument { pub name: Box, pub kind: ProcedureArgumentKind, pub default: ProcedureArgumentDefault, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Arbitrary, Mutate, Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] pub enum ProcedureArgumentKind { StringOrNumber, Boolean, } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Arbitrary, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub enum ProcedureArgumentDefault { String(Box), Boolean(bool), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[arbitrary_context] pub struct ProcedureArgumentRef { pub procedure: ProcedureId, // TODO: make this an arena ID, and make ProcedureContext @@ -385,6 +400,7 @@ pub struct StructuredTarget { } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[arbitrary_context] pub struct Script { pub hat: Option, pub position: Option, @@ -393,24 +409,28 @@ pub struct Script { pub top_reporter: Option, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Arbitrary, Deserialize, Serialize)] +#[arbitrary_context] pub struct ScriptPosition { pub x: i32, pub y: i32, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[arbitrary_context] pub enum Hat { WhenFlagClicked, WhenBroadcastReceived { broadcast: BroadcastId, }, + #[arbitrary(skip)] ProcedureDefinition { procedure: ProcedureId, }, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[arbitrary_context] pub enum Statement { SetVariable { variable: VariableRef, @@ -581,12 +601,14 @@ pub enum Statement { } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[arbitrary_context] pub enum ProcedureInput { Value(Option), Predicate(Option), } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Arbitrary, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[arbitrary_context] pub enum StopOption { All, ThisScript, @@ -594,6 +616,7 @@ pub enum StopOption { } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[arbitrary_context] pub enum Value { Variable(VariableRef), ListContents(ListRef), @@ -647,6 +670,7 @@ pub enum Value { } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[arbitrary_context] pub enum Predicate { LessThan(Option>, Option>), Equals(Option>, Option>), @@ -671,14 +695,16 @@ pub enum Predicate { KeyPressed(Option>), } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Arbitrary, Debug, Clone, PartialEq, Deserialize, Serialize)] +#[arbitrary_context] pub enum Literal { Number(f64), String(Box), Color(Box), } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Arbitrary, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[arbitrary_context] pub enum MathOperator { Abs, Floor, @@ -694,4 +720,35 @@ pub enum MathOperator { Log, Exp, Pow10, -} \ No newline at end of file +} + +#[cfg(feature = "fuzz")] +impl HasLen for StructuredProject { + fn len(&self) -> usize { + let sb3: Sb3Project = self.clone().try_into().unwrap(); + serde_json::to_vec(&sb3).unwrap().len() + } +} + +#[cfg(feature = "fuzz")] +impl HasTargetBytes for StructuredProject { + fn target_bytes(&'_ self) -> libafl_bolts::ownedref::OwnedSlice<'_, u8> { + serde_json::to_vec(&Sb3Project::try_from(self.clone()).unwrap()) + .unwrap() + .into() + } +} + +#[cfg(feature = "fuzz")] +impl core::hash::Hash for StructuredProject { + fn hash(&self, state: &mut H) { + state.write( + serde_json::to_vec(&Sb3Project::try_from(self.clone()).unwrap()) + .unwrap() + .as_slice(), + ) + } +} + +#[cfg(feature = "fuzz")] +impl Input for StructuredProject {} diff --git a/sb3/src/structured/arbitrary.rs b/sb3/src/structured/arbitrary.rs new file mode 100644 index 00000000..2e3f44c5 --- /dev/null +++ b/sb3/src/structured/arbitrary.rs @@ -0,0 +1,510 @@ +use core::marker::PhantomData; +use std::ops::ControlFlow; + +pub use ::arbitrary::{Arbitrary, MaxRecursionReached, Result, Unstructured, details, size_hint}; + +use super::*; + +pub trait ArbitraryContext {} + +macro_rules! ac { + ($($ty:ty),+ $(,)?) => { + $(impl<'a, Ctx: ArbitraryContext> ArbitraryWithContext<'a, Ctx> for $ty { + fn arbitrary_with_context(u: &mut Unstructured<'a>, _context: &Ctx) -> arbitrary::Result { + u.arbitrary() + } + })+ + }; +} + +ac!( + u8, + u16, + u32, + u64, + u128, + usize, + i8, + i16, + i32, + i64, + i128, + isize, + f32, + f64, + bool, + char, + (), + &'a str, + String, + Box +); + +#[derive(Clone)] +pub struct ProjectContext { + pub global_variables: Registry, + pub global_lists: Registry, + pub broadcasts: Registry, + pub num_targets: usize, +} + +impl ArbitraryContext for ProjectContext {} + +#[derive(Clone)] +pub struct TargetContext { + pub global_variables: Registry, + pub global_lists: Registry, + pub local_variables: Registry, + pub local_lists: Registry, + pub broadcasts: Registry, + pub procedures: Registry, + pub num_targets: usize, +} + +impl ArbitraryContext for TargetContext {} + +impl From for ProjectContext { + fn from( + TargetContext { + broadcasts, + global_lists, + global_variables, + num_targets, + .. + }: TargetContext, + ) -> Self { + Self { + broadcasts, + global_lists, + global_variables, + num_targets, + } + } +} + +pub trait ArbitraryWithContext<'a, Ctx: ArbitraryContext>: Sized { + fn arbitrary_with_context(u: &mut Unstructured<'a>, context: &Ctx) -> arbitrary::Result; +} + +struct ArbitraryContextIter<'a, 'b, 'c, Ctx: ArbitraryContext, ElementType> { + u: &'b mut Unstructured<'a>, + ctx: &'c Ctx, + _marker: PhantomData, +} + +impl<'a, Ctx: ArbitraryContext, ElementType: ArbitraryWithContext<'a, Ctx>> Iterator + for ArbitraryContextIter<'a, '_, '_, Ctx, ElementType> +{ + type Item = Result; + fn next(&mut self) -> Option> { + let keep_going = self.u.arbitrary().unwrap_or(false); + if keep_going { + Some(ArbitraryWithContext::arbitrary_with_context( + self.u, self.ctx, + )) + } else { + None + } + } +} + +trait UnstructuredWithContext<'a, Ctx: ArbitraryContext> { + fn arbitrary_iter_ctx<'b, 'c, ElementType: ArbitraryWithContext<'a, Ctx>>( + &'b mut self, + context: &'c Ctx, + ) -> Result>; +} + +impl<'a, Ctx: ArbitraryContext> UnstructuredWithContext<'a, Ctx> for Unstructured<'a> { + fn arbitrary_iter_ctx<'b, 'c, ElementType: ArbitraryWithContext<'a, Ctx>>( + &'b mut self, + context: &'c Ctx, + ) -> Result> { + Ok(ArbitraryContextIter { + u: &mut *self, + ctx: context, + _marker: PhantomData, + }) + } +} + +impl<'a, Ctx: ArbitraryContext, A: ArbitraryWithContext<'a, Ctx>> ArbitraryWithContext<'a, Ctx> + for Option +{ + fn arbitrary_with_context(u: &mut Unstructured<'a>, context: &Ctx) -> arbitrary::Result { + if u.ratio(1, 8)? { + Ok(None) + } else { + Ok(Some(ArbitraryWithContext::arbitrary_with_context( + u, context, + )?)) + } + } +} + +impl<'a, Ctx: ArbitraryContext, A: ArbitraryWithContext<'a, Ctx>> ArbitraryWithContext<'a, Ctx> + for Box +where + Ctx: Into, +{ + fn arbitrary_with_context(u: &mut Unstructured<'a>, context: &Ctx) -> arbitrary::Result { + Ok(Box::new(ArbitraryWithContext::arbitrary_with_context( + u, context, + )?)) + } +} + +impl<'a, Ctx: ArbitraryContext, A: ArbitraryWithContext<'a, Ctx>> ArbitraryWithContext<'a, Ctx> + for Vec +where + Ctx: Into, +{ + fn arbitrary_with_context(u: &mut Unstructured<'a>, context: &Ctx) -> arbitrary::Result { + u.arbitrary_iter_ctx(context)?.collect() + } +} + +macro_rules! arbitrary_context { + (@arbitrary_func $context:ident @ $($expr:expr;)*) => { + fn arbitrary_with_context(u: &mut Unstructured<'a>, context: &$context) -> arbitrary::Result { + #[allow(unused)] + let choices = [ + $($expr),* + ]; + u.choose(&choices)?(u, context) + } + }; + + // base case + ($context:ident @ $pub:vis enum $id:ident + (@variants $($tt:tt)*) + (@builders $($builder:expr;)*) + ) => { + $pub enum $id { + $($tt)* + } + + impl<'a> ArbitraryWithContext<'a, $context> for $id { + arbitrary_context!(@arbitrary_func $context @ $($builder;)*); + } + }; + // recursive case: unit variant + ($context:ident @ $pub:vis enum $id:ident + (@variants $($tt:tt)*) + (@builders $($builder:expr;)*) + $unit:ident, + $($rest:tt)* + ) => { + arbitrary_context!( + $context @ $pub enum $id + (@variants $($tt)* $unit,) + (@builders + $($builder;)* + |u: &mut _, context| Ok($id::$unit); + ) + $($rest)* + ); + }; + ($context:ident @ $pub:vis enum $id:ident + (@variants $($tt:tt)*) + (@builders $($builder:expr;)*) + #[arbitrary(skip)] + $unit:ident, + $($rest:tt)* + ) => { + arbitrary_context!( + $context @ $pub enum $id + (@variants $($tt)* $unit,) + (@builders $($builder;)*) + $($rest)* + ); + }; + + // recursive case: tuple variant + ($context:ident @ $pub:vis enum $id:ident + (@variants $($tt:tt)*) + (@builders $($builder:expr;)*) + $name:ident($($ty:ty),*), + $($rest:tt)* + ) => { + arbitrary_context!( + $context @ $pub enum $id + (@variants $($tt)* $name($($ty),*),) + (@builders + $($builder;)* + |u: &mut _, context| Ok($id::$name($(<$ty as ArbitraryWithContext<'a, $context>>::arbitrary_with_context(u, context)?),*)); + ) + $($rest)* + ); + }; + ($context:ident @ $pub:vis enum $id:ident + (@variants $($tt:tt)*) + (@builders $($builder:expr;)*) + #[arbitrary(skip)] + $name:ident($($ty:ty),*), + $($rest:tt)* + ) => { + arbitrary_context!( + $context @ $pub enum $id + (@variants $($tt)* $name($($ty),*),) + (@builders $($builder;)*) + $($rest)* + ); + }; + + // recursive case: struct variant + ($context:ident @ $pub:vis enum $id:ident + (@variants $($tt:tt)*) + (@builders $($builder:expr;)*) + $name:ident { $($field:ident: $ty:ty),* $(,)? }, + $($rest:tt)* + ) => { + arbitrary_context!( + $context @ $pub enum $id + (@variants $($tt)* $name { $($field: $ty),* },) + (@builders + $($builder;)* + |u: &mut _, context| Ok($id::$name { + $($field: <$ty as ArbitraryWithContext<'a, $context>>::arbitrary_with_context(u, context)?),* + }); + ) + $($rest)* + ); + }; + ($context:ident @ $pub:vis enum $id:ident + (@variants $($tt:tt)*) + (@builders $($builder:expr;)*) + #[arbitrary(skip)] + $name:ident { $($field:ident: $ty:ty),* $(,)? }, + $($rest:tt)* + ) => { + arbitrary_context!( + $context @ $pub enum $id + (@variants $($tt)* $name { $($field: $ty),* },) + (@builders $($builder;)*) + $($rest)* + ); + }; + + // entry points + attr() ($item:item) => { + #[arbitrary_context(TargetContext)] + $item + }; + attr($context:ident) ($pub:vis enum $id:ident { + $($tt:tt)+ + }) => { + arbitrary_context!( + $context @ $pub enum $id + (@variants) + (@builders) + $($tt)+ + ); + }; + + attr($context:ident) ($pub:vis struct $id:ident { + $($fieldpub:vis $field:ident: $ty:ty),+ $(,)? + }) => { + $pub struct $id { + $($fieldpub $field: $ty),+ + } + + impl<'a> ArbitraryWithContext<'a, $context> for $id { + fn arbitrary_with_context(u: &mut Unstructured<'a>, context: &$context) -> arbitrary::Result { + Ok(Self { + $($field: <$ty as ArbitraryWithContext<'a, $context>>::arbitrary_with_context(u, context)?),+ + }) + } + } + }; +} + +pub(super) use arbitrary_context; + +impl<'a> Arbitrary<'a> for StructuredProject { + fn arbitrary(u: &mut Unstructured<'a>) -> Result { + let global_variables: Registry<_, _> = + u.arbitrary_iter::()?.collect::>()?; + let global_lists: Registry<_, _> = u.arbitrary_iter::()?.collect::>()?; + let broadcasts: Registry<_, _> = u.arbitrary_iter::()?.collect::>()?; + + let mut project_context = ProjectContext { + global_lists: global_lists.clone(), + global_variables: global_variables.clone(), + broadcasts: broadcasts.clone(), + num_targets: 0, + }; + + let mut targets: Vec = Vec::new(); + u.arbitrary_loop(None, Some(6), |u| { + targets.push(ArbitraryWithContext::arbitrary_with_context( + u, + &project_context, + )?); + project_context.num_targets += 1; + Ok(ControlFlow::Continue(())) + })?; + + Ok(Self { + global_variables, + global_lists, + broadcasts, + targets, + monitors: Arbitrary::arbitrary(u)?, + extensions: vec![], // TODO + meta: Meta { + semver: "3.0.0".into(), + vm: "3.0.0".into(), + agent: "hyperquark-fuzzer".into(), + }, + }) + } +} + +impl<'a> ArbitraryWithContext<'a, ProjectContext> for StructuredTarget { + fn arbitrary_with_context( + u: &mut Unstructured<'a>, + ProjectContext { + global_variables, + global_lists, + broadcasts, + num_targets, + }: &ProjectContext, + ) -> arbitrary::Result { + let mut local_variables = Registry::new(); + let mut local_lists = Registry::new(); + if *num_targets > 0 { + local_variables = u.arbitrary_iter::()?.collect::>()?; + local_lists = u.arbitrary_iter::()?.collect::>()?; + } + + let local_procedures: Registry<_, _> = + u.arbitrary_iter::()?.collect::>()?; + + let target_context = TargetContext { + global_variables: global_variables.clone(), + global_lists: global_lists.clone(), + broadcasts: broadcasts.clone(), + num_targets: *num_targets, + local_variables: local_variables.clone(), + local_lists: local_lists.clone(), + procedures: local_procedures.clone(), + }; + + let mut scripts = Vec::with_capacity(local_procedures.items.len()); + for procedure in 0..local_procedures.items.len() { + let procedure = ProcedureId(procedure); + scripts.push(Script { + hat: Some(Hat::ProcedureDefinition { procedure }), + position: Arbitrary::arbitrary(u)?, + body: ArbitraryWithContext::arbitrary_with_context(u, &target_context)?, + top_reporter: None, + // TODO: use procedure context instead + }); + } + scripts.extend( + u.arbitrary_iter_ctx(&target_context)? + .collect::>>()?, + ); + + Ok(StructuredTarget { + is_stage: *num_targets == 0, + name: if *num_targets == 0 { + "Stage".into() + } else { + Arbitrary::arbitrary(u)? + }, + local_variables, + local_lists, + local_procedures, + scripts, + comments: Vec::new(), + current_costume: Arbitrary::arbitrary(u)?, + costumes: Arbitrary::arbitrary(u)?, + sounds: Arbitrary::arbitrary(u)?, + layer_order: Arbitrary::arbitrary(u)?, + volume: Arbitrary::arbitrary(u)?, + tempo: Arbitrary::arbitrary(u)?, + video_state: Arbitrary::arbitrary(u)?, + video_transparency: Arbitrary::arbitrary(u)?, + text_to_speech_language: Arbitrary::arbitrary(u)?, + visible: Arbitrary::arbitrary(u)?, + x: Arbitrary::arbitrary(u)?, + y: Arbitrary::arbitrary(u)?, + size: Arbitrary::arbitrary(u)?, + direction: Arbitrary::arbitrary(u)?, + draggable: Arbitrary::arbitrary(u)?, + rotation_style: Arbitrary::arbitrary(u)?, + }) + } +} + +macro_rules! subtype_arbitrary { + ($sup:ident -> $sub:ident for $id:ident) => { + impl<'a> ArbitraryWithContext<'a, $sub> for $id { + fn arbitrary_with_context( + u: &mut Unstructured<'a>, + context: &$sub, + ) -> arbitrary::Result { + ArbitraryWithContext::<$sup>::arbitrary_with_context(u, &context.clone().into()) + } + } + }; +} + +impl<'a> ArbitraryWithContext<'a, ProjectContext> for BroadcastId { + fn arbitrary_with_context( + u: &mut Unstructured<'a>, + context: &ProjectContext, + ) -> arbitrary::Result { + Ok(Self(u.choose_index(context.broadcasts.items.len())?)) + } +} +subtype_arbitrary!(ProjectContext -> TargetContext for BroadcastId); + +impl<'a> ArbitraryWithContext<'a, ProjectContext> for GlobalVariableId { + fn arbitrary_with_context( + u: &mut Unstructured<'a>, + context: &ProjectContext, + ) -> arbitrary::Result { + Ok(Self(u.choose_index(context.global_variables.items.len())?)) + } +} +subtype_arbitrary!(ProjectContext -> TargetContext for GlobalVariableId); + +impl<'a> ArbitraryWithContext<'a, ProjectContext> for GlobalListId { + fn arbitrary_with_context( + u: &mut Unstructured<'a>, + context: &ProjectContext, + ) -> arbitrary::Result { + Ok(Self(u.choose_index(context.global_lists.items.len())?)) + } +} +subtype_arbitrary!(ProjectContext -> TargetContext for GlobalListId); + +impl<'a> ArbitraryWithContext<'a, TargetContext> for ProcedureId { + fn arbitrary_with_context( + u: &mut Unstructured<'a>, + context: &TargetContext, + ) -> arbitrary::Result { + Ok(Self(u.choose_index(context.procedures.items.len())?)) + } +} + +impl<'a> ArbitraryWithContext<'a, TargetContext> for LocalVariableId { + fn arbitrary_with_context( + u: &mut Unstructured<'a>, + context: &TargetContext, + ) -> arbitrary::Result { + Ok(Self(u.choose_index(context.local_variables.items.len())?)) + } +} + +impl<'a> ArbitraryWithContext<'a, TargetContext> for LocalListId { + fn arbitrary_with_context( + u: &mut Unstructured<'a>, + context: &TargetContext, + ) -> arbitrary::Result { + Ok(Self(u.choose_index(context.local_lists.items.len())?)) + } +} diff --git a/sb3/src/structured/mutate.rs b/sb3/src/structured/mutate.rs new file mode 100644 index 00000000..35c67def --- /dev/null +++ b/sb3/src/structured/mutate.rs @@ -0,0 +1,425 @@ +use mutatis::{Candidates, DefaultMutate, Mutate, mutators as ms}; + +use super::*; + +/// A mutator that won't ever grow the input (other than for fields that use a default mutator) +#[derive(Default)] +pub struct StructuredShrinkMutator; + +macro_rules! m { + ($t:ty) => { + impl DefaultMutate for $t { + type DefaultMutate = StructuredShrinkMutator; + } + }; +} + +fn mutate_option( + m: &mut M, + c: &mut Candidates<'_>, + opt: &mut Option, +) -> mutatis::Result<()> +where + M: Mutate, +{ + if opt.is_some() { + c.mutation(|_ctx| { + *opt = None; + Ok(()) + })?; + m.mutate(c, opt.as_mut().unwrap())?; + } + Ok(()) +} + +impl Mutate for StructuredShrinkMutator { + fn mutate(&mut self, c: &mut Candidates<'_>, lit: &mut Literal) -> mutatis::Result<()> { + match lit { + Literal::Number(n) => ms::f64().mutate(c, n)?, + Literal::String(s) => { + let mut st = s.to_string(); + ms::string(ms::char()).mutate(c, &mut st)?; + c.mutation(|_ctx| { + *s = st.clone().into_boxed_str(); + Ok(()) + })? + } + Literal::Color(_) => (), + }; + Ok(()) + } +} +m! {Literal} + +impl Mutate for StructuredShrinkMutator { + fn mutate(&mut self, c: &mut Candidates<'_>, pred: &mut Predicate) -> mutatis::Result<()> { + use Predicate as P; + let boxed = &mut ms::boxed(self); + let mut replacement_candidates = vec![]; + match pred { + P::LessThan(left, right) | P::Equals(left, right) | P::GreaterThan(left, right) => { + mutate_option(boxed, c, left)?; + mutate_option(boxed, c, right)?; + if let Some(l) = left + && let Value::Predicate(l) = *l.clone() + { + replacement_candidates.push(*l.clone()); + } + if let Some(r) = right + && let Value::Predicate(r) = *r.clone() + { + replacement_candidates.push(*r.clone()); + } + } + P::And(left, right) | P::Or(left, right) => { + mutate_option(boxed, c, left)?; + mutate_option(boxed, c, right)?; + if let Some(l) = left { + replacement_candidates.push(*l.clone()); + } + if let Some(r) = right { + replacement_candidates.push(*r.clone()); + } + } + P::Not(input) => { + mutate_option(boxed, c, input)?; + if let Some(i) = input { + replacement_candidates.push(*i.clone()); + } + } + P::MouseDown | P::ProcedureArgument(_) => (), + P::ListContainsItem { item: input, .. } + | P::ItemOfList { index: input, .. } + | P::ItemNumOfList { item: input, .. } + | P::KeyPressed(input) => { + mutate_option(boxed, c, input)?; + if let Some(i) = input + && let Value::Predicate(i) = *i.clone() + { + replacement_candidates.push(*i.clone()); + } + } + }; + if !replacement_candidates.is_empty() { + c.mutation(|ctx| { + let repl = ctx.rng().choose(&replacement_candidates).unwrap(); + *pred = repl.clone(); + Ok(()) + })?; + } + Ok(()) + } +} +m! {Predicate} + +impl Mutate for StructuredShrinkMutator { + fn mutate(&mut self, c: &mut Candidates<'_>, value: &mut Value) -> mutatis::Result<()> { + use Value as V; + let boxed = &mut ms::boxed(self); + let mut replacement_candidates = vec![]; + match value { + V::Literal(literal) => ms::default::().mutate(c, literal)?, + V::Predicate(pred) => boxed.mutate(c, pred)?, + V::Add(left, right) + | V::Subtract(left, right) + | V::Multiply(left, right) + | V::Divide(left, right) + | V::Random(left, right) + | V::Join(left, right) + | V::LetterOf { + letter: left, + text: right, + } + | V::Contains { + text: left, + search: right, + } + | V::Modulo(left, right) => { + mutate_option(boxed, c, left)?; + mutate_option(boxed, c, right)?; + if let Some(l) = left { + replacement_candidates.push(*l.clone()); + } + if let Some(r) = right { + replacement_candidates.push(*r.clone()); + } + } + V::Length(input) + | V::Round(input) + | V::MathOp { operand: input, .. } + | V::ItemOfList { index: input, .. } + | V::ItemNumOfList { item: input, .. } => { + mutate_option(boxed, c, input)?; + if let Some(i) = input { + replacement_candidates.push(*i.clone()); + } + } + V::LengthOfList(_) + | V::Answer + | V::MouseX + | V::MouseY + | V::Timer + | V::DaysSince2000 + | V::XPosition + | V::YPosition + | V::Direction + | V::Size + | V::CostumeNumber + | V::BackdropNumber + | V::Volume + | V::Variable(_) + | V::ListContents(_) + | V::ProcedureArgument(_) + | V::PenMenuColorParam(_) + | V::KeyOptions(_) => (), + } + if !replacement_candidates.is_empty() { + c.mutation(|ctx| { + let repl = ctx.rng().choose(&replacement_candidates).unwrap(); + *value = repl.clone(); + Ok(()) + })?; + } + Ok(()) + } +} +m! {Value} + +impl Mutate for StructuredShrinkMutator { + fn mutate( + &mut self, + c: &mut Candidates<'_>, + input: &mut ProcedureInput, + ) -> mutatis::Result<()> { + match input { + ProcedureInput::Value(value) => mutate_option(self, c, value)?, + ProcedureInput::Predicate(pred) => mutate_option(self, c, pred)?, + } + Ok(()) + } +} +m! {ProcedureInput} + +fn shrink_mutate_vec( + m: &mut M, + c: &mut Candidates<'_>, + vec: &mut Vec, +) -> mutatis::Result<()> +where + M: Mutate, +{ + // Remove an element. + if !vec.is_empty() { + c.mutation(|ctx| { + let index = ctx.rng().gen_index(vec.len()).unwrap(); + vec.remove(index); + Ok(()) + })?; + } + + // Mutate an existing element. + for x in vec { + m.mutate(c, x)?; + } + + Ok(()) +} + +impl Mutate for StructuredShrinkMutator { + fn mutate(&mut self, c: &mut Candidates<'_>, stack: &mut Statement) -> mutatis::Result<()> { + use Statement as S; + match stack { + S::SetVariable { value, .. } + | S::ChangeVariable { value, .. } + | S::Say { message: value } + | S::SwitchCostumeTo { costume: value } + | S::SwitchBackdropTo { backdrop: value } + | S::SwitchBackdropToAndWait { backdrop: value } + | S::ChangeSizeBy { amount: value } + | S::SetSizeTo { size: value } + | S::MoveSteps { steps: value } + | S::AddToList { item: value, .. } + | S::DeleteOfList { index: value, .. } + | S::Wait { duration: value } + | S::AskAndWait { question: value } + | S::Think { message: value } + | S::TurnRight { degrees: value } + | S::TurnLeft { degrees: value } + | S::PointInDirection { direction: value } + | S::ChangeXBy { amount: value } + | S::SetX { value } + | S::ChangeYBy { amount: value } + | S::SetY { value } + | S::PenSetColorToColor { value } + | S::PenSetSizeTo { value } => mutate_option(self, c, value)?, + S::NextCostume + | S::NextBackdrop + | S::ShowVariable { .. } + | S::HideVariable { .. } + | S::DeleteAllOfList { .. } + | S::ShowList { .. } + | S::HideList { .. } + | S::Stop { .. } + | S::Broadcast { .. } + | S::BroadcastAndWait { .. } + | S::ResetTimer + | S::Show + | S::Hide + | S::PenDown + | S::PenUp + | S::PenClear => (), + S::InsertAtList { + index: left, + item: right, + .. + } + | S::ReplaceItemOfList { + index: left, + item: right, + .. + } + | S::SayForSecs { + message: left, + seconds: right, + } + | S::ThinkForSecs { + message: left, + seconds: right, + } + | S::GoToXY { x: left, y: right } + | S::PenChangeColorParamBy { + param: left, + value: right, + } + | S::PenSetColorParamTo { + param: left, + value: right, + } => { + mutate_option(self, c, left)?; + mutate_option(self, c, right)?; + } + S::WaitUntil { condition: pred } => mutate_option(self, c, pred)?, + S::If { + condition: pred, + body: substack, + } + | S::RepeatUntil { + condition: pred, + body: substack, + } + | S::While { + condition: pred, + body: substack, + } => { + mutate_option(self, c, pred)?; + shrink_mutate_vec(self, c, substack)?; + } + S::IfElse { + condition: pred, + then_body: substack1, + else_body: substack2, + } => { + mutate_option(self, c, pred)?; + shrink_mutate_vec(self, c, substack1)?; + shrink_mutate_vec(self, c, substack2)?; + } + S::Repeat { + times: value, + body: substack, + } => { + mutate_option(self, c, value)?; + shrink_mutate_vec(self, c, substack)?; + } + S::Forever { body: substack } => shrink_mutate_vec(self, c, substack)?, + S::CallProcedure { arguments, .. } => { + for arg in arguments { + self.mutate(c, arg)?; + } + } + } + Ok(()) + } +} +m! {Statement} + +impl Mutate