From bdebfde8531ec166c3d50367c0fdfa8be2ef16f4 Mon Sep 17 00:00:00 2001 From: daphneherlambda <94461321+daphneherlambda@users.noreply.github.com> Date: Wed, 30 Aug 2023 12:35:50 -0300 Subject: [PATCH] create vm diff fuzzer (#1358) * create vm diff fuzzer * Working fuzzer * Change name and paths * Delete moved files * Add deps and run commands * Add info to readme * Prototype new fuzzer * generalize program * Add cairo program generator * Integrate with diff fuzzer * generalize program * Consider felt case * generalize parsing for structs and felts * Add inout vars case * add select rand hint code * Delete uint256_mul_div_mod.cairo * Change fuzzer name * Consider more terminations * Change return line * filter hints to try * Begin direct mem comparison * Take any line * Consider struct fields called after var * Remove prefix after picking dictionary * Remove print * Consider pack case * Replace for space instead of deleting * Consider ( after variable * Add constants definition * add extra hint * Check not keccak works and document * fix syntaxis error * add error handling * fix error handling * Refactor cairo_program_gen * Use replace token * Fix parenthesis * Reduce replacing * add documentation * Consider variable in func case * Remove debugging * Load all jsons once * Fix ids.a.field+3 case * Add case for constants * Correct main fuzzer readme * Spelling diff fuzzer docu * Clarify 2. in diff fuzzer docu * Add make deps step * Specify constants and cairo structs importing * Add maturing to deps * Update Makefile * Correct makefile * Correct makefile * Add missing memory checker module * Remove initial var declarations * Update changelog * Run cargo fmt * Use ast for parsing * Document cairo_program_gen * Remove commented code * Update readme doc * Get hints from VM code * Change make command name * Make interrupting more reliable * Add targets to PHONY * assignment -> assignments Co-authored-by: fmoletta <99273364+fmoletta@users.noreply.github.com> * add with Co-authored-by: fmoletta <99273364+fmoletta@users.noreply.github.com> * importing -> to be imported Co-authored-by: fmoletta <99273364+fmoletta@users.noreply.github.com> --------- Co-authored-by: dafifynn Co-authored-by: juan.mv Co-authored-by: Juan-M-V <102986292+Juan-M-V@users.noreply.github.com> Co-authored-by: fmoletta <99273364+fmoletta@users.noreply.github.com> --- CHANGELOG.md | 2 + Makefile | 14 +- fuzzer/Cargo.lock | 404 ++++++++++++++++++++++- fuzzer/Cargo.toml | 8 +- fuzzer/README.md | 6 + fuzzer/diff_fuzzer/README.md | 60 ++++ fuzzer/diff_fuzzer/cairo_program_gen.py | 200 +++++++++++ fuzzer/diff_fuzzer/hint_reader.py | 23 ++ fuzzer/diff_fuzzer/memory_checker.py | 24 ++ fuzzer/diff_fuzzer/random_hint_fuzzer.py | 105 ++++++ fuzzer/src/py_export.rs | 65 ++++ 11 files changed, 897 insertions(+), 14 deletions(-) create mode 100644 fuzzer/diff_fuzzer/README.md create mode 100644 fuzzer/diff_fuzzer/cairo_program_gen.py create mode 100644 fuzzer/diff_fuzzer/hint_reader.py create mode 100644 fuzzer/diff_fuzzer/memory_checker.py create mode 100644 fuzzer/diff_fuzzer/random_hint_fuzzer.py create mode 100644 fuzzer/src/py_export.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5260624deb..0f311d6985 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: Added a differential fuzzer for programs with whitelisted hints [#1358](https://github.com/lambdaclass/cairo-vm/pull/1358) + * fix: Change return type of `get_execution_resources` to `RunnerError` [#1398](https://github.com/lambdaclass/cairo-vm/pull/1398) * Don't build wasm-demo in `build` target + add ci job to run the wasm demo [#1393](https://github.com/lambdaclass/cairo-vm/pull/1393) diff --git a/Makefile b/Makefile index 10ed9eafff..5efdf1c5a8 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ STARKNET_SIERRA_COMPILE_CAIRO_2:=cairo2/bin/starknet-sierra-compile compare_trace_memory_proof compare_all_proof compare_trace_proof compare_memory_proof compare_air_public_input \ cairo_bench_programs cairo_proof_programs cairo_test_programs cairo_1_test_contracts cairo_2_test_contracts \ cairo_trace cairo-vm_trace cairo_proof_trace cairo-vm_proof_trace \ + fuzzer-deps fuzzer-run-cairo-compiled fuzzer-run-hint-diff \ $(RELBIN) $(DBGBIN) # Proof mode consumes too much memory with cairo-lang to execute @@ -309,9 +310,18 @@ clean: rm -rf cairo1 rm -rf cairo2 -fuzzer-deps: +fuzzer-deps: build cargo +nightly install cargo-fuzz + . cairo-vm-env/bin/activate; \ + pip install atheris==2.2.2 maturin==1.2.3; \ + cd fuzzer/; \ + maturin develop -run-cairo-compiled-fuzzer: +fuzzer-run-cairo-compiled: cd fuzzer cargo +nightly fuzz run --fuzz-dir . cairo_compiled_programs_fuzzer + +fuzzer-run-hint-diff: + . cairo-vm-env/bin/activate ; \ + cd fuzzer/diff_fuzzer/; \ + ../../cairo-vm-env/bin/python random_hint_fuzzer.py -len_control=0 diff --git a/fuzzer/Cargo.lock b/fuzzer/Cargo.lock index 6cd9ce3125..3fd0694e0e 100644 --- a/fuzzer/Cargo.lock +++ b/fuzzer/Cargo.lock @@ -123,6 +123,33 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "bitvec" version = "1.0.1" @@ -150,21 +177,28 @@ version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "cairo-felt" -version = "0.8.5" +version = "0.8.7" dependencies = [ "arbitrary", "lazy_static", "num-bigint", "num-integer", "num-traits", + "proptest", "serde", ] [[package]] name = "cairo-vm" -version = "0.8.5" +version = "0.8.7" dependencies = [ "anyhow", "arbitrary", @@ -193,9 +227,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.80" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f1226cd9da55587234753d1245dd5b132343ea240f26b6a9003d68706141ba" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" dependencies = [ "jobserver", "libc", @@ -276,6 +310,39 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "funty" version = "2.0.0" @@ -284,13 +351,14 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "fuzzer" -version = "0.1.0" +version = "0.1.1" dependencies = [ "arbitrary", "cairo-felt", "cairo-vm", "honggfuzz", "libfuzzer-sys", + "pyo3", "serde", "serde_json", ] @@ -365,6 +433,12 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "indoc" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" + [[package]] name = "itertools" version = "0.10.5" @@ -424,15 +498,21 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libfuzzer-sys" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beb09950ae85a0a94b27676cccf37da5ff13f27076aa1adbc6545dd0d0e1bd4e" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" dependencies = [ "arbitrary", "cc", "once_cell", ] +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + [[package]] name = "libmimalloc-sys" version = "0.1.33" @@ -443,6 +523,22 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.19" @@ -473,6 +569,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "mimalloc" version = "0.1.37" @@ -556,6 +661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -564,6 +670,29 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "paste" version = "1.0.14" @@ -585,6 +714,92 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bit-set", + "bitflags 1.3.2", + "byteorder", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "pyo3" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.32" @@ -630,6 +845,30 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "rfc6979" version = "0.4.0" @@ -649,12 +888,43 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "1.0.18" @@ -663,18 +933,18 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.180" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.180" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", @@ -713,6 +983,12 @@ dependencies = [ "keccak", ] +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + [[package]] name = "spin" version = "0.5.2" @@ -814,6 +1090,25 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "target-lexicon" +version = "0.12.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" + +[[package]] +name = "tempfile" +version = "3.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + [[package]] name = "thiserror-impl-no-std" version = "2.0.2" @@ -840,18 +1135,39 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +[[package]] +name = "unindent" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -912,6 +1228,72 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "wyz" version = "0.5.1" diff --git a/fuzzer/Cargo.toml b/fuzzer/Cargo.toml index 8dfae9d9f6..1c53db1811 100644 --- a/fuzzer/Cargo.toml +++ b/fuzzer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fuzzer" -version = "0.1.0" +version = "0.1.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,6 +17,7 @@ honggfuzz = "0.5.55" libfuzzer-sys = "0.4" cairo-vm = { path = "../vm", features = ["arbitrary"] } cairo-felt = { path = "../felt", features = ["arbitrary"] } +pyo3 = { version = "0.19.2", features = ["extension-module"] } serde = { version = "1.0.180", features = ["derive"] } serde_json = "1.0.104" @@ -31,3 +32,8 @@ path = "src/cairo_compiled_programs_fuzzer.rs" [[bin]] name = "fuzz_program" path = "src/fuzz_program.rs" + +[lib] +name = "cairo_vm_rs" +path = "src/py_export.rs" +crate-type = ["cdylib"] diff --git a/fuzzer/README.md b/fuzzer/README.md index 577c2abf5b..ab7ea22d48 100644 --- a/fuzzer/README.md +++ b/fuzzer/README.md @@ -10,3 +10,9 @@ To run the fuzzer you need to have installed `cargo-fuzz`. If not, use the comma To run simply use `cargo +nightly fuzz run --fuzz-dir . cairo_compiled_programs_fuzzer` We use nightly for this fuzzer because cargo fuzz runs with the -Z flag, which only works with +nightly. + +## diff_fuzzer +To run the diff fuzzer on various cairo hints, go to the root of the project and run +`make fuzzer-deps` if you haven't before, this should only be run once. Then, you can call +`make fuzzer-run-hint-diff` to run the fuzzer. +For more documentaion, check out the diff_fuzzer [README](diff_fuzzer/README.md) diff --git a/fuzzer/diff_fuzzer/README.md b/fuzzer/diff_fuzzer/README.md new file mode 100644 index 0000000000..3ad5b5ee13 --- /dev/null +++ b/fuzzer/diff_fuzzer/README.md @@ -0,0 +1,60 @@ +# Random hint fuzzer + +This fuzzer takes a list of pre-selected hints and runs a generated basic program with randomized inputs that uses a random hint from the list. + +## To run the fuzzer + +Use the commands: + - `make fuzzer-deps` to ensure that you have the atheris module and created a python module from `fuzzer/src/py_export.rs` using maturin. + - `make fuzzer-run-hint-diff` to run the fuzzer. + +## The fuzzer +The fuzzer is located in the ***random_hint_fuzzer.py*** file inside the diff_fuzzer folder. + +### How does it work? + +1. first it selects a random hint with the function `get_random_hint`, it has a list of indexes that represent a hint previously saved in a file within ***/hint_accountant/whitelists/***; Selects a random number between the listed ones and returns the list of lines that correspond to that hint code. +2. Creates a basic program that uses the hint calling the function `generate_cairo_hint_program`, it returns a program with a `REPLACEABLE_TOKEN` constant placed wherever a random value has to be placed. +3. Replace all the constants with random values with the function `generate_limb`, this function returns a value with 70% of provability to be a number within ***range_check_max >> 1 and range_check_max***, with range_check_max being ***"340282366920938463463374607431768211456"*** 15% probability of being a number within ***0 and 10*** and 15% probability of being between ***1 and range_check_max***. +4. Creates a random name for the modified Cairo file and the compiled json one. +5. Write the modified cairo program to a file with the generated ***.cairo*** name. +6. Compile the ***.cairo*** file to get the ***.json*** file. +7. Run the ***.json*** program with the Python original vm implementation and the new Rust one. +8. if both implementations run correctly, use the `check_mem` function to compare that the memories are the same. If one implementation returns an error and the other one runs correctly the fuzzer reports an error, as both implementations should return the same output. + +## The program generator + +The program generator is located in the ***cairo_program_gen.py*** file inside the diff_fuzzer folder. + +### How does it work? + +1. Grab a hint given by the fuzzer. +2. Look for all the `ids.(...)` expressions, make sure to keep track of any ***"="*** to the left or right. Also check for cairo constants +3. Reduce the `ids.(...)` expressions so that all the variables are grouped with their fields. +4. After looking at the `ids.(...)`, import `EcPoint`, `BigInt3` or any cairo constants needed for the hint to run. +5. Create dictionaries and classify the variables from step 3 + - declare_in_main: if ***"="*** was to the ***right*** + - declare_in_hint_fn: if ***"="*** was to the ***left*** +6. Create `main` using declare_in_main variables + + ``` + func main() { + let a = MyStruct(field=1, field=2); + hint_func(); + return(); + } + ``` + +7. Create `hint_func` with variables from declare_in_hint_fn as locals + ``` + hint_func(a: MyStruct) -> (MyStruct) { + alloc_locals; + local b: MyStruct; + %{ + ... + %} + return(b); + } + ``` + +8. return the entire generated program diff --git a/fuzzer/diff_fuzzer/cairo_program_gen.py b/fuzzer/diff_fuzzer/cairo_program_gen.py new file mode 100644 index 0000000000..6699518596 --- /dev/null +++ b/fuzzer/diff_fuzzer/cairo_program_gen.py @@ -0,0 +1,200 @@ +import ast +""" +Generate a cairo program with the following rules: + 1. Grab a hint + 2. Look for all the ids.(...) expressions, make sure to keep track of any assignments + 3. Reduce the ids.(...) expressions so that all the variables and their fields are + grouped + 4. Create variables dicts + - declare_in_main: if the variable is used as assignee in the hint + - declare_in_hint_fn: if the variable receives a value + 5. Create a list with all the constants called inside the hint that have to be imported + from a cairo module + 6. Create main using declare_in_main + - func main() { + let a = MyStruct(field=1, field=2); + hint_func(); + return(); + } + 7. Create hint_func with the variables in declare_in_hint_fn as locals + - hint_func(a: MyStruct) -> (MyStruct) { + alloc_locals; + local b: MyStruct; + %{ + ... + %} + return(b); + } + 8. Import all the needed constants and struct names +""" + +CAIRO_TYPES = { "felt", "EcPoint", "BigInt3" } +REPLACEABLE_TOKEN = "__TOKEN_TO_REPLACE__" + +CAIRO_CONSTS = { + "ADDR_BOUND": "from starkware.starknet.common.storage import ADDR_BOUND" +} + +def classify_variables(hint_code): + """ + classify_varables(String) -> (Dict, Dict, List) + return declare_in_main, declare_in_hint_fn, list(consts_to_import) + Grabs a hint text and classifies all the variables associated with the `ids` structure. + - declare_in_main: holds all the variables that should be declared in the `main` cairo + function and that will receive the fuzzer selected values + - declare_in_hint_fn: holds the variables that are assigned a value inside the hint + - consts_to_import: holds the cairo constants that are called inside the hint that + need to be imported form a common library module + The output dictionaries will also keep track of the variable's fields considering the + case where a variable is called inside a `pack` function additionally, `pack` can be + defined inside the hint or imported from the internal ec point functionality. + """ + targets = set() + ids_nodes = [] + pack_call_nodes = [] + pack_function_nodes = [] + for node in ast.walk(ast.parse(hint_code)): + if isinstance(node, ast.Assign): + for assign_node in ast.walk(node): + if isinstance(assign_node, ast.Attribute) and \ + isinstance(assign_node.value, ast.Name) and assign_node.value.id == "ids" and \ + isinstance(assign_node.ctx, ast.Store): + targets.add(assign_node.attr) + elif isinstance(node, ast.Attribute): + ids_nodes.append(node) + elif isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == "pack": + pack_call_nodes.append(node) + elif isinstance(node, ast.FunctionDef) and node.name == "pack": + pack_function_nodes.append(node) + + ids_fields = {node.attr:set() for node in ids_nodes if isinstance(node.value, ast.Name) and node.value.id == "ids"} + for node in ids_nodes: + if isinstance(node.value, ast.Attribute) : + ids_fields[node.value.attr].add(node.attr) + + pack_call_ids = set() + for node in pack_call_nodes: + for arg in node.args: + if isinstance(arg, ast.Attribute): + if isinstance(arg.value, ast.Name) and arg.value.id == "ids": + pack_call_ids.add(arg.attr) + elif isinstance(arg.value, ast.Attribute) and arg.value.value.id == "ids": + pack_call_ids.add(arg.value.attr) + + def_pack_function = "BigInt3" + if pack_function_nodes != []: + z_fields = {b_node.attr for b_node in ast.walk(pack_function_nodes[0]) if isinstance(b_node, ast.Attribute) and b_node.value.id == "z"} + if z_fields == {"low", "high"}: + def_pack_function = "SplitNum" + + declare_in_main = {} + declare_in_hint_fn = {} + consts_to_import = set() + for var_name in ids_fields.keys(): + if var_name in pack_call_ids: + ids_fields[var_name] = frozenset({def_pack_function}) if def_pack_function == "BigInt3" else frozenset({"low", "high"}) + if ids_fields[var_name] == set(): + ids_fields[var_name] = frozenset({"felt"}) + + if var_name in CAIRO_CONSTS: + consts_to_import.add(var_name) + elif var_name in targets: + declare_in_hint_fn[var_name] = frozenset(ids_fields[var_name]) + else: + declare_in_main[var_name] = frozenset(ids_fields[var_name]) + return declare_in_main, declare_in_hint_fn, list(consts_to_import) + +def generate_cairo_hint_program(hint_code): + """ + generate_cairo_hint_program(String) -> [String] + Call `classify_varables(hint_code)` and create a cairo program with all the necessary code to run the hint + code block that was passed as parameter + """ + declare_in_main, declare_in_hint_fn, consts = classify_variables(hint_code) + consts_imports = [ import_line for const, import_line in CAIRO_CONSTS.items() if const in consts ] + + all_types = (declare_in_main | declare_in_hint_fn).values() + + import_ecpoint = "from starkware.cairo.common.cairo_secp.ec import EcPoint\n" if any("EcPoint" in types for types in all_types) else "" + import_bigint3 = "from starkware.cairo.common.cairo_secp.bigint import BigInt3\n" if import_ecpoint != "" or any("BigInt3" in types for types in all_types) else "" + + structs_fields = { f for f in all_types if not f.issubset(CAIRO_TYPES) } + structs_dict = { v : "MyStruct" + str(i) for (i, v) in enumerate(structs_fields) } + + structs_fmt = "struct {struct_name} {{\n{struct_fields}\n}}\n" + fields_fmt = "\t{field_name}: felt," + + declared_structs = "\n".join([ + structs_fmt.format( + struct_name = name, + struct_fields = "\n".join([fields_fmt.format(field_name=field_name) for field_name in fields]) + ) + for (fields, name) in structs_dict.items() if fields != frozenset({"low", "high"}) + ]) + + if frozenset({"low", "high"}) in structs_dict: + declared_structs += "struct {struct_name} {{\n\tlow: felt,\n\thigh: felt,\n}}".format(struct_name=structs_dict[frozenset({"low", "high"})]) + + main_func_fmt = "\nfunc main() {{{variables}\n\thint_func({input_var_names});\n\treturn();\n}}\n" + main_struct_assignment_fmt = "\n\tlet {var_name} = {struct_name}({assign_fields});" + main_ecpoint_assignment_fmt = "\n\tlet {var_name} = EcPoint(BigInt3(d0=" + REPLACEABLE_TOKEN + ", d1=" + REPLACEABLE_TOKEN + ", d2=" + REPLACEABLE_TOKEN + "), BigInt3(d0=" + REPLACEABLE_TOKEN + ", d1=" + REPLACEABLE_TOKEN + ", d2=" + REPLACEABLE_TOKEN + "));" + main_bigint_assignment_fmt = "\n\tlet {var_name} = BigInt3(d0=" + REPLACEABLE_TOKEN + ", d1=" + REPLACEABLE_TOKEN + ", d2=" + REPLACEABLE_TOKEN + ");" + main_low_high_assignment_fmt = "\n\tlet {var_name} = {struct_name}(low=" + REPLACEABLE_TOKEN + ", high=" + REPLACEABLE_TOKEN + ");" + main_var_felt_assingment_fmt = "\n\tlet {var_name} =" + REPLACEABLE_TOKEN + ";" + + main_var_assignments = "" + for name, var_fields in declare_in_main.items(): + if "felt" in var_fields: + main_var_assignments += main_var_felt_assingment_fmt.format(var_name = name) + elif "EcPoint" in var_fields: + main_var_assignments += main_ecpoint_assignment_fmt.format(var_name = name) + elif "BigInt3" in var_fields: + main_var_assignments += main_bigint_assignment_fmt.format(var_name = name) + elif frozenset({"low", "high"}) == var_fields: + main_var_assignments += main_low_high_assignment_fmt.format(var_name = name, struct_name = structs_dict[frozenset({"low", "high"})]) + else: + main_var_assignments += main_struct_assignment_fmt.format( + var_name = name, + struct_name = structs_dict[var_fields], + assign_fields = ", ".join([ + field_name + "=" + REPLACEABLE_TOKEN for field_name in var_fields + ]) + ) + + main_func = main_func_fmt.format( + variables = main_var_assignments, + input_var_names = ", ".join([var for var in declare_in_main.keys()]) + ) + + hint_func_fmt = "\nfunc hint_func{signature} {{\n\talloc_locals;\n{local_declarations}\n%{{\n{hint}\n%}}\n\treturn({output_return});\n}}\n" + local_declare_fmt = "\tlocal {res_var_name}: {res_struct};" + + # To extract an element from a set: element = next(iter(a_set)) + input_vars_signatures = ", ".join([ + name + ": " + (next(iter(var_fields)) if var_fields.issubset(CAIRO_TYPES) else structs_dict[var_fields]) + for name, var_fields in declare_in_main.items() + ]) + output_vars_signatures = ", ".join([ + next(iter(var_fields)) if var_fields.issubset(CAIRO_TYPES) else structs_dict[var_fields] + for var_fields in all_types + ]) + + signature = "(" + input_vars_signatures + ")" + if len(all_types) > 1: + signature += " -> (" + output_vars_signatures + ")" + elif len(all_types) == 1: + signature += " -> " + output_vars_signatures + + local_vars = "\n".join([ + local_declare_fmt.format(res_var_name = name, res_struct = next(iter(var_fields)) if var_fields.issubset(CAIRO_TYPES) else structs_dict[var_fields]) for name, var_fields in declare_in_hint_fn.items() + ]) + + hint_func = hint_func_fmt.format( + signature = signature, + local_declarations = local_vars, + hint = hint_code, + output_return = ", ".join([res for res in (declare_in_main | declare_in_hint_fn).keys()]) + ) + + return import_ecpoint + import_bigint3 + "\n".join(consts_imports) + "\n" + declared_structs + main_func + hint_func + diff --git a/fuzzer/diff_fuzzer/hint_reader.py b/fuzzer/diff_fuzzer/hint_reader.py new file mode 100644 index 0000000000..d922bc4d27 --- /dev/null +++ b/fuzzer/diff_fuzzer/hint_reader.py @@ -0,0 +1,23 @@ +import re + +def load_hints(): + # Match any string with the pattern: + # r"# hint code + # hint code + # ..."#; + # ?: makes sure that the match stops at first "#; aparition + raw_str_pattern = re.compile('r#"(.*?)"#;', re.DOTALL) + # Match any of the string options + # variable << variable + # variable << number literal + # number literal << variable + # number literal << number literal + shift_right_pattern = "[\d+|\w+]\s*<<\s*[\d+|\w+]" + # Match any hint with SECP_P present + secp_p_pattern = "from.*import.*SECP_P|SECP_P\s*=" + filter_pattern = re.compile(f"{shift_right_pattern}|{secp_p_pattern}") + with open("../../vm/src/hint_processor/builtin_hint_processor/hint_code.rs", "r") as f: + hints_code = f.read() + + return [hint_code.group(1) for hint_code in raw_str_pattern.finditer(hints_code) if filter_pattern.search(hint_code.group(1))] + diff --git a/fuzzer/diff_fuzzer/memory_checker.py b/fuzzer/diff_fuzzer/memory_checker.py new file mode 100644 index 0000000000..9b34f4cea2 --- /dev/null +++ b/fuzzer/diff_fuzzer/memory_checker.py @@ -0,0 +1,24 @@ +def check_mem(raw_py_mem, raw_rs_mem): + py_mem = {} + rs_mem = {} + + assert len(raw_py_mem) % 40 == 0, f'Python implementation: malformed memory file' + chunks = len(raw_py_mem) // 40 + for i in range(0, chunks): + chunk = raw_py_mem[i*40:(i+1)*40] + k, v = int.from_bytes(chunk[:8], 'little'), int.from_bytes(chunk[8:], 'little') + assert k not in py_mem, f'Python implementation: address {k} has two values' + py_mem[k] = v + assert len(py_mem) * 40 == len(raw_py_mem), f'Python implementation: {len(py_mem) * 40} != {len(raw_py_mem)}' + + assert len(raw_rs_mem) % 40 == 0, f'Rust implementation: malformed memory file from cairo-vm' + chunks = len(raw_rs_mem) // 40 + for i in range(0, chunks): + chunk = raw_rs_mem[i*40:(i+1)*40] + k, v = int.from_bytes(chunk[:8], 'little'), int.from_bytes(chunk[8:], 'little') + assert k not in rs_mem, f'Rust implementation: address {k} has two values' + rs_mem[k] = v + assert len(rs_mem) * 40 == len(raw_rs_mem), f'Rust implementation: {len(rs_mem) * 40} != {len(raw_rs_mem)}' + + assert rs_mem == py_mem, "Mismatch in memory files" + diff --git a/fuzzer/diff_fuzzer/random_hint_fuzzer.py b/fuzzer/diff_fuzzer/random_hint_fuzzer.py new file mode 100644 index 0000000000..9ae806bbb0 --- /dev/null +++ b/fuzzer/diff_fuzzer/random_hint_fuzzer.py @@ -0,0 +1,105 @@ +#!../cairo-vm-env/bin/python3 +import sys +import atheris +import json +from starkware.cairo.lang.compiler.cairo_compile import compile_cairo +from starkware.cairo.lang.instances import LAYOUTS +from starkware.cairo.lang.vm.cairo_runner import CairoRunner +from starkware.cairo.lang.vm.cairo_run import load_program +from starkware.cairo.lang.vm.memory_dict import MemoryDict +from cairo_program_gen import generate_cairo_hint_program, REPLACEABLE_TOKEN +from cairo_vm_rs import cairo_run_dump_mem, PanicTriggered, VMError +from memory_checker import check_mem +from hint_reader import load_hints + +PRIME = 2**251 + 17 * 2**192 + 1 + +@atheris.instrument_func +def generate_limb(fdp): + range_check_max = PRIME #340282366920938463463374607431768211456 + if fdp.ConsumeProbability() > 0.3: + return fdp.ConsumeIntInRange(range_check_max >> 1, range_check_max) + elif fdp.ConsumeBool(): + return fdp.ConsumeIntInRange(0, 10) + else: + return fdp.ConsumeIntInRange(1, range_check_max) + +def write_failing_file(fdp, cairo_program): + alt_filename = hex(fdp.ConsumeUInt(8)) + filename = "failed_input_" + alt_filename + ".cairo" + with open(filename, 'w', encoding='utf-8') as file: + file.write(cairo_program) + return filename + +@atheris.instrument_func +def diff_fuzzer(data): + fdp = atheris.FuzzedDataProvider(data) + hint = fdp.PickValueInList(LOADED_HINTS) + + try: + cairo_program = generate_cairo_hint_program(hint) + except KeyboardInterrupt: + sys.exit("Fuzzing stopped!") + except: + failed_input_name = write_failing_file(fdp, hint) + sys.exit(f"Failed to generate cairo program from hint. Check file: {failed_input_name}") + + replace_count = cairo_program.count(REPLACEABLE_TOKEN) + for _ in range(replace_count): + cairo_program = cairo_program.replace(REPLACEABLE_TOKEN, str(generate_limb(fdp)), 1) + try: + program = compile_cairo(cairo_program, PRIME) + except KeyboardInterrupt: + sys.exit("Fuzzing stopped!") + except: + failed_input_name = write_failing_file(fdp, cairo_program) + sys.exit(f"Failed to compile cairo program. Check file: {failed_input_name}") + + # Get Rust implementation memory + rust_err = False + try: + raw_rs_mem = cairo_run_dump_mem(json.dumps(program.dump())) + except KeyboardInterrupt: + sys.exit("Fuzzing stopped!") + except PanicTriggered as error: + failed_input_name = write_failing_file(fdp, cairo_program) + sys.exit(f"{error}\nCheck file: {failed_input_name}") + except VMError: + rust_err = True + + # Get Python implementation memory + python_err = False + try: + runner = CairoRunner( + program=program, + layout=LAYOUTS["plain"], + memory=MemoryDict(), + proof_mode=None, + allow_missing_builtins=None, + ) + runner.initialize_segments() + end = runner.initialize_main_entrypoint() + runner.initialize_vm(hint_locals={"program_input": {}}) + runner.run_until_pc(end) + runner.end_run() + runner.relocate() + raw_py_mem = list(runner.relocated_memory.serialize(32)) + except KeyboardInterrupt: + sys.exit("Fuzzing stopped!") + except: + python_err = True + + if not (rust_err or python_err): + try: + check_mem(raw_py_mem=raw_py_mem, raw_rs_mem=raw_rs_mem) + except AssertionError: + failed_input_name = write_failing_file(fdp, cairo_program) + sys.exit(f"Memory files differ. Check file: {failed_input_name}") + elif rust_err != python_err: + write_failing_file(fdp, cairo_program) + assert rust_err == python_err, f"Rust is error: {rust_err}, Python is error: {python_err}" + +LOADED_HINTS = load_hints() +atheris.Setup(sys.argv, diff_fuzzer) +atheris.Fuzz() + diff --git a/fuzzer/src/py_export.rs b/fuzzer/src/py_export.rs new file mode 100644 index 0000000000..070b233de7 --- /dev/null +++ b/fuzzer/src/py_export.rs @@ -0,0 +1,65 @@ +use cairo_vm::{ + cairo_run::{cairo_run, CairoRunConfig}, + hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, +}; +use pyo3::{ + create_exception, + exceptions::PyException, + prelude::{pyfunction, pymodule, wrap_pyfunction, PyModule, PyResult, Python}, +}; +use std::panic; + +create_exception!( + cairo_vm_rs, + PanicTriggered, + PyException, + "Raised when panic_unwind catches a panic during `cairo_run`" +); +create_exception!( + cairo_vm_rs, + VMError, + PyException, + "`cairo_run` raised a `CairoRunError`" +); + +#[pyfunction] +fn cairo_run_dump_mem(json: String) -> PyResult> { + let config = CairoRunConfig { + relocate_mem: true, + ..Default::default() + }; + + let result_no_panic = panic::catch_unwind(|| { + cairo_run( + &json.as_bytes(), + &config, + &mut BuiltinHintProcessor::new_empty(), + ) + }) + .map_err(|e| { + PanicTriggered::new_err(format! {"Rust VM panicked! {:?}", e.downcast::()}) + })?; + + let (cairo_runner, _) = + result_no_panic.map_err(|e| VMError::new_err(format! {"VM error: {:?}", e.to_string()}))?; + + let mut memory_dump = Vec::new(); + for (i, memory_cell) in cairo_runner.relocated_memory.iter().enumerate() { + match memory_cell { + None => continue, + Some(unwrapped_memory_cell) => { + memory_dump.extend_from_slice(&(i as u64).to_le_bytes()); + memory_dump.extend_from_slice(&unwrapped_memory_cell.to_le_bytes()); + } + } + } + Ok(memory_dump) +} + +#[pymodule] +fn cairo_vm_rs(py: Python, m: &PyModule) -> PyResult<()> { + m.add("PanicTriggered", py.get_type::())?; + m.add("VMError", py.get_type::())?; + m.add_function(wrap_pyfunction!(cairo_run_dump_mem, m)?)?; + Ok(()) +}