From 3017b88e27849830530fc0ccf77df215889d17ba Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Wed, 13 Mar 2024 11:35:27 +0100 Subject: [PATCH] fuzz: fuzzing integrations, fuzz the descriptors module This integrates fuzzing into our project by introducing two targets which exercise the descriptor parsing and analysis logic. The `descriptor_parse` is dead simple but not very effective. The `descriptors` harness tries to be smarter by almost always generating a valid Liana descriptor. Of course, this is just a first integration and both could be made more effective. --- .gitignore | 1 + fuzz/Cargo.lock | 747 ++++++++++++++++++++++++++ fuzz/Cargo.toml | 31 ++ fuzz/README.md | 5 + fuzz/fuzz_targets/descriptor_parse.rs | 76 +++ fuzz/fuzz_targets/descriptors.rs | 192 +++++++ 6 files changed, 1052 insertions(+) create mode 100644 fuzz/Cargo.lock create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/README.md create mode 100644 fuzz/fuzz_targets/descriptor_parse.rs create mode 100644 fuzz/fuzz_targets/descriptors.rs diff --git a/.gitignore b/.gitignore index 0798b8783..ebd68fb4b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ gui/ui/Cargo.lock gui/ui/examples/design-system/Cargo.lock Xcode_12.2.xip .idea/ +fuzz/corpus diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock new file mode 100644 index 000000000..d011ec9e3 --- /dev/null +++ b/fuzz/Cargo.lock @@ -0,0 +1,747 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bdk_coin_select" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0320167c3655e83f0415d52f39618902e449186ffc7dfb090f922f79675c316" + +[[package]] +name = "bech32" +version = "0.10.0-beta" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea" + +[[package]] +name = "bip39" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" +dependencies = [ + "bitcoin_hashes 0.11.0", + "serde", + "unicode-normalization", +] + +[[package]] +name = "bitcoin" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd00f3c09b5f21fb357abe32d29946eb8bb7a0862bae62c0b5e4a692acbbe73c" +dependencies = [ + "base64 0.21.7", + "bech32", + "bitcoin-internals", + "bitcoin_hashes 0.13.0", + "hex-conservative", + "hex_lit", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" + +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fern" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" +dependencies = [ + "log", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "hex-conservative" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jobserver" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +dependencies = [ + "libc", +] + +[[package]] +name = "jsonrpc" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26d9104d516092f092d97448787505881fdb6518293b2d6500bf9c180c839dd" +dependencies = [ + "base64 0.13.1", + "minreq", + "serde", + "serde_json", +] + +[[package]] +name = "liana" +version = "4.0.0" +dependencies = [ + "backtrace", + "bdk_coin_select", + "bip39", + "dirs", + "fern", + "getrandom", + "jsonrpc", + "libc", + "log", + "miniscript", + "rdrand", + "rusqlite", + "serde", + "serde_json", + "toml", +] + +[[package]] +name = "liana-fuzz" +version = "0.0.0" +dependencies = [ + "arbitrary", + "liana", + "libfuzzer-sys", + "secp256k1", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "miniscript" +version = "11.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a23dd3ad145a980e231185d114399f25a0a307d2cd918010ddda6334323df9" +dependencies = [ + "bech32", + "bitcoin", + "bitcoin-internals", + "serde", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "minreq" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3371dfc7b772c540da1380123674a8e20583aca99907087d990ca58cf44203" +dependencies = [ + "log", + "serde", + "serde_json", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rdrand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92195228612ac8eed47adbc2ed0f04e513a4ccb98175b6f2bd04d963b533655" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "rusqlite" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d" +dependencies = [ + "bitflags 2.4.2", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "secp256k1" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +dependencies = [ + "bitcoin_hashes 0.13.0", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 000000000..4a944dced --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "liana-fuzz" +version = "0.0.0" +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +arbitrary = { version = "1", features = ["derive"] } +secp256k1 = { version = "0.28", features = ["global-context-less-secure"] } + + +[dependencies.liana] +path = ".." + +[[bin]] +name = "descriptor_parse" +path = "fuzz_targets/descriptor_parse.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "descriptors" +path = "fuzz_targets/descriptors.rs" +test = false +doc = false +bench = false diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 000000000..bc656c67b --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,5 @@ +# Liana fuzzing targets + +This folder hosts the fuzz targets for Liana. We simply use +[`cargo-fuzz`](https://github.com/rust-fuzz/cargo-fuzz) for now (see there for instructions on how +to run the targets). diff --git a/fuzz/fuzz_targets/descriptor_parse.rs b/fuzz/fuzz_targets/descriptor_parse.rs new file mode 100644 index 000000000..9d7563a57 --- /dev/null +++ b/fuzz/fuzz_targets/descriptor_parse.rs @@ -0,0 +1,76 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; + +use liana::{descriptors::LianaDescriptor, miniscript::bitcoin::Network}; +use secp256k1::global::SECP256K1; + +use std::str::{self, FromStr}; + +// Hacky way to detect too deeply nested wrappers, which would make rust-miniscript stack overflow +// when parsing them. +fn too_deep_wrappers(desc_str: &str) -> bool { + let mut wrapper_count = 0; + for c in desc_str.chars() { + if c == ':' || c == '(' || c == ')' || c == ',' { + if c == ':' && wrapper_count > 10 { + return true; + } + wrapper_count = 0; + } else { + wrapper_count += 1; + } + } + false +} + +fuzz_target!(|data: &[u8]| { + let desc_str = match str::from_utf8(data) { + Ok(s) => s, + Err(_) => return, + }; + if data.len() > 10_000 { + return; + } + + // Rust-miniscript uses a recursive parsing algorithm which could make us crash when trying to + // parse nested fragments. Until they fix it, rule out the most trivial recursion overflow the + // fuzzer can generate: too deep wrappers. + // FIXME: this shouldn't be necessary when upgrading to the next rust-miniscript version. + if too_deep_wrappers(&desc_str) { + return; + } + + let desc = match LianaDescriptor::from_str(desc_str) { + Ok(d) => d, + Err(_) => return, + }; + + // The descriptor must roundtrip. + assert_eq!(desc, LianaDescriptor::from_str(&desc.to_string()).unwrap()); + + // We can get the policy out of this desc and a desc out of this policy, but it's not + // guaranteed to roundtrip: policy->descriptor involves a compilation. + LianaDescriptor::new(desc.policy()); + + // Exercise the various methods on the descriptor. None should crash. + desc.receive_descriptor(); + desc.change_descriptor(); + desc.first_timelock_value(); + desc.max_sat_weight(); + desc.max_sat_vbytes(); + desc.spender_input_size(); + + // Exercise the various methods of derived descriptors. None should crash. + let der_index = 42.into(); + let der_descs = [ + desc.receive_descriptor().derive(der_index, &SECP256K1), + desc.change_descriptor().derive(der_index, &SECP256K1), + ]; + for desc in der_descs { + desc.address(Network::Bitcoin); + desc.script_pubkey(); + desc.witness_script(); + desc.bip32_derivations(); + } +}); diff --git a/fuzz/fuzz_targets/descriptors.rs b/fuzz/fuzz_targets/descriptors.rs new file mode 100644 index 000000000..61eaeca20 --- /dev/null +++ b/fuzz/fuzz_targets/descriptors.rs @@ -0,0 +1,192 @@ +#![no_main] + +use arbitrary::{Arbitrary, Unstructured}; +use libfuzzer_sys::fuzz_target; + +use liana::{ + descriptors::{LianaDescriptor, LianaPolicy, PathInfo}, + miniscript::{ + bitcoin::{bip32, Network, Psbt}, + descriptor, + }, +}; +use secp256k1::global::SECP256K1; + +use std::{collections::BTreeMap, str::FromStr}; + +#[derive(Arbitrary, Debug)] +struct PathConfig { + pub thresh: u8, + pub count: u8, +} + +impl PathConfig { + /// Generate the data for this path. We reuse the same xpub across the board as it doesn't + /// matter. However we change the fingerprint, as it matters for the spend info analysis. + pub fn info(&self, path_index: u8) -> Option { + if self.thresh > self.count || self.count > 100 { + // Not worth wasting time generating pathological or huge policies. + return None; + } + + let public_key = secp256k1::PublicKey::from_str( + "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + ) + .expect("Valid pubkey: NUMS from BIP341"); + let dummy_fg = [0, 0, path_index, 0].into(); + let xpub = bip32::Xpub { + network: Network::Bitcoin, + depth: 0, + parent_fingerprint: dummy_fg, + child_number: 0.into(), + public_key, + chain_code: [path_index; 32].into(), + }; + + if self.count == 1 { + Some(PathInfo::Single( + descriptor::DescriptorPublicKey::MultiXPub(descriptor::DescriptorMultiXKey { + origin: Some((dummy_fg, vec![0.into()].into())), + xkey: xpub, + derivation_paths: descriptor::DerivPaths::new(vec![ + vec![0.into()].into(), + vec![1.into()].into(), + ]) + .unwrap(), + wildcard: descriptor::Wildcard::Unhardened, + }), + )) + } else { + let keys: Vec<_> = (0..self.count) + .map(|i| { + descriptor::DescriptorPublicKey::MultiXPub(descriptor::DescriptorMultiXKey { + origin: Some(([0, 0, path_index, i].into(), vec![0.into()].into())), + xkey: xpub, + derivation_paths: descriptor::DerivPaths::new(vec![ + vec![(i as u32).into()].into(), + vec![(i as u32 + 1).into()].into(), + ]) + .unwrap(), + wildcard: descriptor::Wildcard::Unhardened, + }) + }) + .collect(); + Some(PathInfo::Multi(self.thresh.into(), keys)) + } + } +} + +#[derive(Arbitrary, Debug)] +struct RecPathConfig { + pub timelock: u8, + pub path: PathConfig, +} + +#[derive(Debug)] +struct DummyPsbt(pub Option); + +impl<'a> Arbitrary<'a> for DummyPsbt { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + let psbt_bytes = <&[u8]>::arbitrary(u)?; + Ok(Self(Psbt::deserialize(&psbt_bytes).ok())) + } +} + +// TODO: be smarter instead of parsing a PSBT, have a config which generates an interesting PSBT. +/// The configuration for this target: the descriptor policy to be used, and various things we use +/// in exercising the various methods of the descriptor. +#[derive(Arbitrary, Debug)] +struct Config { + pub prim_path: PathConfig, + pub recovery_paths: Vec, + pub der_index: u16, + pub dummy_psbt: DummyPsbt, +} + +fuzz_target!(|config: Config| { + // Not worth wasting time compiling huge policies. + if config.recovery_paths.len() > 100 { + return; + } + + // Generate the descriptor according to the policy in the config. + let prim_path_info = if let Some(info) = config.prim_path.info(0) { + info + } else { + return; + }; + let rec_paths_info: Option> = config + .recovery_paths + .into_iter() + .enumerate() + .map(|(idx, rec_conf)| Some((rec_conf.timelock.into(), rec_conf.path.info(idx as u8 + 1)?))) + .collect(); + let rec_paths_info = if let Some(info) = rec_paths_info { + info + } else { + return; + }; + let policy = if let Ok(policy) = LianaPolicy::new(prim_path_info, rec_paths_info) { + policy + } else { + return; + }; + let desc = LianaDescriptor::new(policy); + + // The descriptor must roundtrip. + assert_eq!(desc, LianaDescriptor::from_str(&desc.to_string()).unwrap()); + + // We can get the policy out of this desc and a desc out of this policy, but it's not + // guaranteed to roundtrip: policy->descriptor involves a compilation. + LianaDescriptor::new(desc.policy()); + + // Exercise the various methods on the descriptor. None should crash. + desc.receive_descriptor(); + desc.change_descriptor(); + desc.first_timelock_value(); + desc.max_sat_weight(); + desc.max_sat_vbytes(); + desc.spender_input_size(); + + // Exercise the various methods of derived descriptors. None should crash. + let der_index = (config.der_index as u32).into(); + let der_descs = [ + desc.receive_descriptor().derive(der_index, &SECP256K1), + desc.change_descriptor().derive(der_index, &SECP256K1), + ]; + for desc in &der_descs { + desc.address(Network::Bitcoin); + desc.script_pubkey(); + desc.witness_script(); + desc.bip32_derivations(); + } + + // Exercise the methods gathering information from a PSBT. TODO: get more useful PSBTs. + if let Some(mut psbt) = config.dummy_psbt.0 { + desc.change_indexes(&psbt, &SECP256K1); + + // Get the spend info without populating the PSBT at all. + let _ = desc.partial_spend_info(&psbt); + + // Populate the PSBT. We arbitrarily use the receive desc for inputs and the change desc + // for outputs. + let rec_desc = &der_descs[0]; + for psbt_in in psbt.inputs.iter_mut() { + psbt_in.witness_script = Some(rec_desc.witness_script()); + psbt_in.bip32_derivation = rec_desc.bip32_derivations(); + } + let change_desc = &der_descs[1]; + for psbt_out in psbt.outputs.iter_mut() { + psbt_out.bip32_derivation = change_desc.bip32_derivations(); + } + + // Now get the spend info again with these info. + let _ = desc.partial_spend_info(&psbt); + + // Prune all the info but those for the latest available path, and get the spend info + // again. + if let Ok(psbt) = desc.prune_bip32_derivs_last_avail(psbt) { + let _ = desc.partial_spend_info(&psbt); + } + } +});