diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c2def34..fca6c63 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -82,4 +82,17 @@ jobs: - run: rustup component add clippy - run: rustup target add thumbv7m-none-eabi - name: Build - run: cd lm3s6965-demo && cargo build && cargo clippy --all-features --workspace -- -D warnings + run: cd demos/lm3s6965-demo && cargo build && cargo clippy --all-features --workspace -- -D warnings + + libafl: + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - nightly + steps: + - uses: actions/checkout@v4 + - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + - run: rustup component add clippy + - run: cargo install just + - run: cd demos/libafl-demo && just fuzzer \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 7a6eb22..c52c05a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,12 +62,56 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + [[package]] name = "anyhow" version = "1.0.99" @@ -176,6 +220,26 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + [[package]] name = "bitbybit" version = "1.3.2" @@ -221,6 +285,27 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cc" +version = "1.2.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.3" @@ -260,23 +345,49 @@ dependencies = [ "half", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" -version = "4.5.48" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -294,6 +405,12 @@ dependencies = [ "thiserror 2.0.16", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "common" version = "0.0.0" @@ -495,6 +612,12 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + [[package]] name = "equivalent" version = "1.0.2" @@ -512,6 +635,16 @@ dependencies = [ "typeid", ] +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.1", +] + [[package]] name = "fandango" version = "0.1.0" @@ -587,6 +720,7 @@ dependencies = [ "hashbrown 0.16.0", "num-rational", "rand 0.9.2", + "serde", ] [[package]] @@ -600,6 +734,12 @@ dependencies = [ "wide", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + [[package]] name = "fixedbitset" version = "0.5.7" @@ -667,6 +807,12 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "half" version = "2.6.0" @@ -684,6 +830,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", + "allocator-api2", "serde", ] @@ -710,6 +857,12 @@ dependencies = [ "serde", ] +[[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.1" @@ -740,6 +893,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.13.0" @@ -755,6 +914,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.81" @@ -801,6 +970,42 @@ dependencies = [ "windows", ] +[[package]] +name = "libafl-demo-clang" +version = "0.1.0" +dependencies = [ + "clap", + "fandango", + "fandango-targets", + "libafl", + "libafl_bolts", + "libafl_cc", + "libafl_fandango", + "libafl_targets", + "mappable-rc", + "mimalloc", + "rand 0.9.2", + "serde", +] + +[[package]] +name = "libafl-demo-xml" +version = "0.1.0" +dependencies = [ + "clap", + "fandango", + "fandango-targets", + "libafl", + "libafl_bolts", + "libafl_cc", + "libafl_fandango", + "libafl_targets", + "mappable-rc", + "mimalloc", + "rand 0.9.2", + "serde", +] + [[package]] name = "libafl_bolts" version = "0.15.3" @@ -835,18 +1040,52 @@ dependencies = [ "windows-result", ] +[[package]] +name = "libafl_cc" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f448c9b9216c9c3fa459393e9509b8dee6049160615d1c969bec5427a6d6fe05" +dependencies = [ + "cc", + "glob", + "serde", + "which", +] + [[package]] name = "libafl_fandango" version = "0.1.0" dependencies = [ "fandango", + "fandango-runtime", + "fandango-targets", "libafl", "libafl_bolts", "mappable-rc", + "num-rational", "rand 0.9.2", "serde", ] +[[package]] +name = "libafl_targets" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26bda6ba23651205f19243586b7408c17129d04aeafdcf97ed58c694e6b81b9a" +dependencies = [ + "bindgen", + "cc", + "hashbrown 0.14.5", + "libafl", + "libafl_bolts", + "libc", + "log", + "once_cell", + "rangemap", + "rustversion", + "serde", +] + [[package]] name = "libafl_wide" version = "0.7.33" @@ -863,12 +1102,32 @@ version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.0", +] + [[package]] name = "libm" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "libmimalloc-sys" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "linfa" version = "0.7.1" @@ -911,6 +1170,12 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "lock_api" version = "0.4.14" @@ -976,6 +1241,21 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mimalloc" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1013,6 +1293,16 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -1092,6 +1382,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "oorandom" version = "11.1.5" @@ -1237,6 +1533,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.101" @@ -1332,6 +1638,12 @@ dependencies = [ "serde", ] +[[package]] +name = "rangemap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7e49bb0bf967717f7bd674458b3d6b0c5f48ec7e3038166026a69fc22223" + [[package]] name = "rawpointer" version = "0.2.1" @@ -1412,6 +1724,25 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.1", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -1551,6 +1882,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "siphasher" version = "1.0.1" @@ -1599,6 +1936,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.106" @@ -1761,6 +2104,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.18.1" @@ -1882,6 +2231,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "which" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +dependencies = [ + "either", + "env_home", + "rustix", + "winsafe", +] + [[package]] name = "wide" version = "0.7.33" @@ -1914,7 +2275,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.1", ] [[package]] @@ -1998,6 +2359,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.1" @@ -2072,6 +2442,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wit-bindgen" version = "0.45.1" diff --git a/Cargo.toml b/Cargo.toml index a311746..5029709 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,10 +48,10 @@ members = [ "baselines/scriptsizec", "baselines/xml", "libafl", + "demos/libafl-demo-xml", + "demos/libafl-demo-clang", ] -default-members = [ - ".", "core", "derive", "targets", "generator", "runtime" -] +default-members = [".", "core", "derive", "targets", "generator", "runtime"] resolver = "3" [workspace.package] @@ -67,11 +67,13 @@ embedded-io = { version = "0.6.1", features = ["alloc"] } foldhash = { version = "0.2.0", default-features = false } hashbrown = "0.16.0" libafl = { version = "0.15.3", default-features = false } +libafl_cc = { version = "0.15.3", default-features = false } +libafl_targets = { version = "0.15.3", default-features = false } libafl_bolts = { version = "0.15.3", default-features = false } linfa = "0.7.1" linfa-linear = "0.7.1" mappable-rc = { version = "0.1.1", default-features = false } -ndarray = "0.15.6" # based on what linfa supports +ndarray = "0.15.6" # based on what linfa supports num-integer = { version = "0.1.46", default-features = false } num-rational = { version = "0.4.2", default-features = false } num-traits = { version = "0.2.19", default-features = false } diff --git a/demos/libafl-demo-clang/.gitignore b/demos/libafl-demo-clang/.gitignore new file mode 100644 index 0000000..723b0ba --- /dev/null +++ b/demos/libafl-demo-clang/.gitignore @@ -0,0 +1,4 @@ +fuzzer_stats.json +*.log +llvm +llvm.zip \ No newline at end of file diff --git a/demos/libafl-demo-clang/Cargo.toml b/demos/libafl-demo-clang/Cargo.toml new file mode 100644 index 0000000..a8cf721 --- /dev/null +++ b/demos/libafl-demo-clang/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "libafl-demo-clang" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[features] +default = ["std"] +std = ["libafl/std", "libafl_bolts/std", "libafl_bolts/serdeany_autoreg"] + +[dependencies] +fandango = { path = "../.." } +fandango-targets = { path = "../../targets", features = [ + "scriptsizec", + "serde", +] } + +libafl_cc = { workspace = true } +libafl = { workspace = true, features = ["rand_trait"] } +libafl_bolts = { workspace = true, features = ["rand_trait"] } +libafl_targets = { workspace = true, features = [ + "coverage", + "common", + "libfuzzer", + "sancov_pcguard_edges", +] } +libafl_fandango = { path = "../../libafl" } +mappable-rc.workspace = true +serde = { workspace = true, features = ["alloc", "derive"] } +mimalloc = "0.1.48" +clap = { version = "4.5.51", features = ["derive"] } +rand = { workspace = true, features = ["std_rng"] } + +[lints] +workspace = true + +[lib] +name = "libafl_example" +crate-type = ["staticlib"] diff --git a/demos/libafl-demo-clang/Justfile b/demos/libafl-demo-clang/Justfile new file mode 100644 index 0000000..187bc78 --- /dev/null +++ b/demos/libafl-demo-clang/Justfile @@ -0,0 +1,60 @@ +[unix] +cc: + cargo build --release --bin libafl_cc --target-dir target + +[unix] +cxx: + cargo build --release --bin libafl_cxx --target-dir target + +CC_WRAPPER := "target/release/libafl_cc" +CXX_WRAPPER := "target/release/libafl_cxx" + + +[unix] +source_tarball: cc cxx + if [ ! -f llvm.zip ]; then \ + curl -L https://github.com/llvm/llvm-project/archive/refs/heads/main.zip -o llvm.zip ; \ + fi + +[unix] +source: source_tarball + if [ ! -f llvm/README.md ]; then \ + rm -rf llvm && \ + unzip -q llvm.zip && \ + mv llvm-project-main llvm ; \ + fi + +[unix] +fuzzer_lib: + LIBAFL_EDGES_MAP_ALLOCATED_SIZE=16777216 LIBAFL_EDGES_MAP_DEFAULT_SIZE=16777216 cargo build --release --target-dir target + + +[unix] +build: source fuzzer_lib + cd llvm && \ + mkdir -p build && cd build && \ + cmake -GNinja -DCMAKE_BUILD_TYPE=Release ../llvm \ + -DLLVM_ENABLE_PROJECTS="clang;lld;clang-tools-extra" \ + -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;compiler-rt" \ + -DCMAKE_C_COMPILER="$(realpath ../../target/release/libafl_cc)" \ + -DCMAKE_CXX_COMPILER="$(realpath ../../target/release/libafl_cxx)" \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DLLVM_LIB_FUZZING_ENGINE="$(realpath ../../target/release/liblibafl_example.a)" \ + -DLLVM_NO_DEAD_STRIP=ON \ + -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly \ + -DCOMPILER_RT_INCLUDE_TESTS=OFF && \ + ninja clang-fuzzer -j $(nproc); + +[unix] +run: build + llvm/build/bin/clang-fuzzer --cores all + # -DCMAKE_C_FLAGS="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fno-pie -fno-PIE" \ + # -DCMAKE_CXX_FLAGS="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fno-pie -fno-PIE" \ + # -DLLVM_USE_SANITIZE_COVERAGE=ON \ + # -DLLVM_USE_LINKER=lld \ + # -DCMAKE_C_COMPILER="${CC}" \ + # -DCMAKE_CXX_COMPILER="${CXX}" \ + # -DCMAKE_C_FLAGS="${CFLAGS}" \ + # -DCMAKE_CXX_FLAGS="${CXXFLAGS}" \ + # -DLLVM_USE_SANITIZER="${LLVM_SANITIZER}" \ + diff --git a/demos/libafl-demo-clang/README.md b/demos/libafl-demo-clang/README.md new file mode 100644 index 0000000..04ad2ce --- /dev/null +++ b/demos/libafl-demo-clang/README.md @@ -0,0 +1,7 @@ +# Example of using fandango-rs on clang + +``` +just run +``` + +is your friend. \ No newline at end of file diff --git a/demos/libafl-demo-clang/src/bin/libafl_cc.rs b/demos/libafl-demo-clang/src/bin/libafl_cc.rs new file mode 100644 index 0000000..47a837c --- /dev/null +++ b/demos/libafl-demo-clang/src/bin/libafl_cc.rs @@ -0,0 +1,51 @@ +//! CC +use std::env; + +use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper}; + +/// Run the CC wrapper +/// +/// # Panics +/// +/// Panics if the compiler fails +pub fn main() { + let args: Vec = env::args().collect(); + if args.len() > 1 { + let mut dir = env::current_exe().unwrap(); + let wrapper_name = dir.file_name().unwrap().to_str().unwrap(); + + let is_cpp = match wrapper_name[wrapper_name.len() - 2..] + .to_lowercase() + .as_str() + { + "cc" => false, + "++" | "pp" | "xx" => true, + _ => panic!( + "Could not figure out if c or c++ wrapper was called. Expected {} to end with c or cxx", + dir.display() + ), + }; + + dir.pop(); + + let mut cc = ClangWrapper::new(); + let cc = cc + .cpp(is_cpp) + // silence the compiler wrapper output, needed for some configure scripts. + .silence(true) + .parse_args(&args) + .expect("Failed to parse the command line"); + + cc.link_staticlib(&dir, "libafl_example"); + + if let Some(code) = cc + .add_arg("-fsanitize-coverage=trace-pc-guard") + .run() + .expect("Failed to run the wrapped compiler") + { + std::process::exit(code); + } + } else { + panic!("LibAFL CC: No Arguments given"); + } +} diff --git a/demos/libafl-demo-clang/src/bin/libafl_cxx.rs b/demos/libafl-demo-clang/src/bin/libafl_cxx.rs new file mode 100644 index 0000000..3a2ada6 --- /dev/null +++ b/demos/libafl-demo-clang/src/bin/libafl_cxx.rs @@ -0,0 +1,6 @@ +//! CXX +mod libafl_cc; + +fn main() { + libafl_cc::main(); +} diff --git a/demos/libafl-demo-clang/src/lib.rs b/demos/libafl-demo-clang/src/lib.rs new file mode 100644 index 0000000..92d5ae3 --- /dev/null +++ b/demos/libafl-demo-clang/src/lib.rs @@ -0,0 +1,218 @@ +//! A libafl example fuzzer + +extern crate alloc; +use core::time::Duration; +use std::{env, path::PathBuf}; + +use clap::Parser; +use fandango::visitor::{Visitor as _, write::WriteVisitor}; +use fandango_targets::scriptsizec::nonterminal_start; +use libafl::{ + Error, + corpus::{InMemoryCorpus, OnDiskCorpus}, + events::{EventConfig, EventRestarter, Launcher, LlmpRestartingEventManager}, + executors::{ExitKind, inprocess::InProcessExecutor}, + feedback_or, feedback_or_fast, + feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + monitors::{MultiMonitor, OnDiskJsonMonitor}, + observers::{CanTrack, HitcountsMapObserver, StdMapObserver, TimeObserver}, + schedulers::StdScheduler, + stages::{StdMutationalStage, calibrate::CalibrationStage}, + state::{HasRand, StdState}, +}; +use libafl_bolts::{ + core_affinity::Cores, + rands::StdRand, + shmem::{ShMemProvider as _, StdShMemProvider}, + tuples::tuple_list, +}; +use libafl_fandango::{ + generator::FandangoGenerator, inputs::DerivationTree, mutators::AdvanceMutator, +}; +use libafl_targets::{EDGES_MAP, MAX_EDGES_FOUND, libfuzzer_initialize, libfuzzer_test_one_input}; +use mimalloc::MiMalloc; +use rand::{RngCore, SeedableRng as _, rngs::StdRng}; + +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + +/// Optional libFuzzer hook for custom mutation. +/// This is a stub implementation that returns the original size (no mutation). +#[unsafe(no_mangle)] +pub extern "C" fn LLVMFuzzerCustomMutator( + _data: *mut u8, + size: usize, + _max_size: usize, + _seed: u32, +) -> usize { + // Return original size to indicate no mutation was performed + size +} + +/// Optional libFuzzer hook for custom crossover. +/// This is a stub implementation that returns 0 to indicate it's not used. +#[unsafe(no_mangle)] +pub extern "C" fn LLVMFuzzerCustomCrossOver( + _data1: *const u8, + _size1: usize, + _data2: *const u8, + _size2: usize, + _out: *mut u8, + _max_out_size: usize, + _seed: u32, +) -> usize { + 0 +} + +#[derive(Parser)] +struct Opt { + #[arg(short, long, default_value = "./crashes")] + objective_dir: PathBuf, + #[arg(short, long, default_value = "./fuzzer_stats.json")] + stats_file: PathBuf, + #[arg(short, long)] + stdout_file: Option, + #[arg(short, long, default_value = "1337")] + broker_port: u16, + #[arg(short, long, default_value = "0", value_parser = Cores::from_cmdline)] + cores: Cores, +} + +/// The main fn, `no_mangle` as it is a C main +#[unsafe(no_mangle)] +#[expect(clippy::missing_panics_doc, clippy::too_many_lines)] +pub extern "C" fn libafl_main() { + let opt = Opt::parse(); + + let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); + + let monitor = tuple_list!( + OnDiskJsonMonitor::new(opt.stats_file, |_| true), + MultiMonitor::new(|s| println!("{s}")) + ); + + let mut run_client = |state: Option<_>, + mut restarting_mgr: LlmpRestartingEventManager<_, _, _, _, _>, + _client_description| { + let objective_dir = opt.objective_dir.clone(); + + #[allow(static_mut_refs)] // only a problem on nightly + let edges_observer = unsafe { + HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( + "edges", + EDGES_MAP.as_mut_ptr(), + MAX_EDGES_FOUND, + )) + .track_indices() + }; + + let time_observer = TimeObserver::new("time"); + let map_feedback = MaxMapFeedback::new(&edges_observer); + // let kpath_feedback = KPathFeedback::new(nonzero!(nonterminal_start::DISCRIMINANT)); + let calibration = CalibrationStage::new(&map_feedback); + + let mut feedback = feedback_or!( + map_feedback, + // kpath_feedback, + TimeFeedback::new(&time_observer) + ); + + let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + + let mut state = state.unwrap_or_else(|| { + StdState::new( + StdRand::new(), + InMemoryCorpus::new(), + OnDiskCorpus::new(objective_dir).unwrap(), + &mut feedback, + &mut objective, + ) + .unwrap() + }); + + let mutator = AdvanceMutator::new( + StdRng::seed_from_u64(state.rand_mut().next_u64()), + (), + "AdvanceMutator", + ); + + let mutational_stage = StdMutationalStage::new(mutator); + + let mut stages = tuple_list!(calibration, mutational_stage); + + let scheduler = StdScheduler::new(); + + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + let mut harness = |input: &DerivationTree| { + let linearized = WriteVisitor::new(Vec::new()) + .visit(input.node(), 0) + .unwrap() + .continue_value() + .unwrap() + .output(); + + unsafe { + libfuzzer_test_one_input(&linearized); + } + ExitKind::Ok + }; + + // Create the executor for an in-process function with one observer for edge coverage and one for the execution time + let mut executor = InProcessExecutor::with_timeout( + &mut harness, + tuple_list!(edges_observer, time_observer), + &mut fuzzer, + &mut state, + &mut restarting_mgr, + Duration::new(10, 0), + )?; + + let args: Vec = env::args().collect(); + if unsafe { libfuzzer_initialize(&args) } == -1 { + println!("Warning: LLVMFuzzerInitialize failed with -1"); + } + + if state.must_load_initial_inputs() { + let sampler = StdRng::seed_from_u64(state.rand_mut().next_u64()); + let mut generator = FandangoGenerator::new(sampler, ()); + state.generate_initial_inputs( + &mut fuzzer, + &mut executor, + &mut generator, + &mut restarting_mgr, + 10, + )?; + } + + let iters = 1_000_000; + fuzzer.fuzz_loop_for( + &mut stages, + &mut executor, + &mut state, + &mut restarting_mgr, + iters, + )?; + + restarting_mgr.on_restart(&mut state)?; + + Ok(()) + }; + + match Launcher::builder() + .shmem_provider(shmem_provider) + .configuration(EventConfig::from_name("default")) + .monitor(monitor) + .run_client(&mut run_client) + .cores(&opt.cores) + .broker_port(opt.broker_port) + .stdout_file(opt.stdout_file.as_deref()) + .build() + .launch() + { + Ok(()) => (), + Err(Error::ShuttingDown) => println!("Fuzzing stopped by user. Goodbye."), + Err(err) => panic!("Failed to run launcher: {err:?}"), + } +} diff --git a/demos/libafl-demo-xml/.gitignore b/demos/libafl-demo-xml/.gitignore new file mode 100644 index 0000000..0bc6413 --- /dev/null +++ b/demos/libafl-demo-xml/.gitignore @@ -0,0 +1,3 @@ +fuzzer_stats.json +*.log +libxml2 diff --git a/demos/libafl-demo-xml/Cargo.toml b/demos/libafl-demo-xml/Cargo.toml new file mode 100644 index 0000000..b126ebe --- /dev/null +++ b/demos/libafl-demo-xml/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "libafl-demo-xml" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[features] +default = ["std"] +std = ["libafl/std", "libafl_bolts/std", "libafl_bolts/serdeany_autoreg"] + +[dependencies] +fandango = { path = "../.." } +fandango-targets = { path = "../../targets", features = ["xml", "serde"] } + +libafl_cc = { workspace = true } +libafl = { workspace = true, features = ["rand_trait"] } +libafl_bolts = { workspace = true, features = ["rand_trait"] } +libafl_targets = { workspace = true, features = [ + "coverage", + "common", + "libfuzzer", + "sancov_pcguard_edges", +] } +libafl_fandango = { path = "../../libafl" } +mappable-rc.workspace = true +serde = { workspace = true, features = ["alloc", "derive"] } +mimalloc = "0.1.48" +clap = { version = "4.5.51", features = ["derive"] } +rand = { workspace = true, features = ["std_rng"] } + +[lints] +workspace = true + +[lib] +name = "libafl_example" +crate-type = ["staticlib"] diff --git a/demos/libafl-demo-xml/Justfile b/demos/libafl-demo-xml/Justfile new file mode 100644 index 0000000..0dc7370 --- /dev/null +++ b/demos/libafl-demo-xml/Justfile @@ -0,0 +1,56 @@ +[unix] +cc: + cargo build --release --bin libafl_cc --target-dir target + +[unix] +cxx: + cargo build --release --bin libafl_cxx --target-dir target + +CC_WRAPPER := "target/release/libafl_cc" +CXX_WRAPPER := "target/release/libafl_cxx" + + +[unix] +source: cc cxx + if [ ! -f libxml2/Makefile ]; then \ + rm -rf libxml2 && \ + git clone https://gitlab.gnome.org/GNOME/libxml2.git && \ + ( cd libxml2 && \ + ./autogen.sh --without-python && \ + ./configure --enable-static --disable-shared ); \ + fi + +[unix] +fuzzer_lib: + cargo build --release --target-dir target + +[unix] +lib_clean: + cd libxml2 && \ + make clean + + +[unix] +lib: source fuzzer_lib + cd libxml2 && \ + make -j$(nproc) \ + CC="$(realpath ../{{CC_WRAPPER}})" \ + CXX="$(realpath ../{{CXX_WRAPPER}})" \ + CFLAGS="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fno-pie -fno-PIE" \ + LDFLAGS="-no-pie" + +[unix] +fuzzer: lib + $(realpath {{CC_WRAPPER}}) -O2 -g -std=c11 \ + -I libxml2/include \ + libxml2/fuzz/xml.c libxml2/fuzz/fuzz.c \ + libxml2/.libs/libxml2.a \ + -lz -llzma -lpthread \ + -o target/release/fuzzer + +[unix] +run *args: fuzzer + target/release/fuzzer {{args}} + +clean: + rm -rf libxml2 target ../target \ No newline at end of file diff --git a/demos/libafl-demo-xml/src/bin/libafl_cc.rs b/demos/libafl-demo-xml/src/bin/libafl_cc.rs new file mode 100644 index 0000000..47a837c --- /dev/null +++ b/demos/libafl-demo-xml/src/bin/libafl_cc.rs @@ -0,0 +1,51 @@ +//! CC +use std::env; + +use libafl_cc::{ClangWrapper, CompilerWrapper, ToolWrapper}; + +/// Run the CC wrapper +/// +/// # Panics +/// +/// Panics if the compiler fails +pub fn main() { + let args: Vec = env::args().collect(); + if args.len() > 1 { + let mut dir = env::current_exe().unwrap(); + let wrapper_name = dir.file_name().unwrap().to_str().unwrap(); + + let is_cpp = match wrapper_name[wrapper_name.len() - 2..] + .to_lowercase() + .as_str() + { + "cc" => false, + "++" | "pp" | "xx" => true, + _ => panic!( + "Could not figure out if c or c++ wrapper was called. Expected {} to end with c or cxx", + dir.display() + ), + }; + + dir.pop(); + + let mut cc = ClangWrapper::new(); + let cc = cc + .cpp(is_cpp) + // silence the compiler wrapper output, needed for some configure scripts. + .silence(true) + .parse_args(&args) + .expect("Failed to parse the command line"); + + cc.link_staticlib(&dir, "libafl_example"); + + if let Some(code) = cc + .add_arg("-fsanitize-coverage=trace-pc-guard") + .run() + .expect("Failed to run the wrapped compiler") + { + std::process::exit(code); + } + } else { + panic!("LibAFL CC: No Arguments given"); + } +} diff --git a/demos/libafl-demo-xml/src/bin/libafl_cxx.rs b/demos/libafl-demo-xml/src/bin/libafl_cxx.rs new file mode 100644 index 0000000..3a2ada6 --- /dev/null +++ b/demos/libafl-demo-xml/src/bin/libafl_cxx.rs @@ -0,0 +1,6 @@ +//! CXX +mod libafl_cc; + +fn main() { + libafl_cc::main(); +} diff --git a/demos/libafl-demo-xml/src/lib.rs b/demos/libafl-demo-xml/src/lib.rs new file mode 100644 index 0000000..ef67eda --- /dev/null +++ b/demos/libafl-demo-xml/src/lib.rs @@ -0,0 +1,190 @@ +//! A libafl example fuzzer + +extern crate alloc; +use core::time::Duration; +use std::{env, path::PathBuf}; + +use clap::Parser; +use fandango::visitor::{Visitor as _, write::WriteVisitor}; +use fandango_targets::xml::nonterminal_start; +use libafl::{ + Error, + corpus::{InMemoryCorpus, OnDiskCorpus}, + events::{EventConfig, EventRestarter, Launcher, LlmpRestartingEventManager}, + executors::{ExitKind, inprocess::InProcessExecutor}, + feedback_or, feedback_or_fast, + feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + monitors::{MultiMonitor, OnDiskJsonMonitor}, + observers::{CanTrack, HitcountsMapObserver, StdMapObserver, TimeObserver}, + schedulers::StdScheduler, + stages::{StdMutationalStage, calibrate::CalibrationStage}, + state::{HasRand, StdState}, +}; +use libafl_bolts::{ + core_affinity::Cores, + rands::StdRand, + shmem::{ShMemProvider as _, StdShMemProvider}, + tuples::tuple_list, +}; +use libafl_fandango::{ + generator::FandangoGenerator, inputs::DerivationTree, mutators::AdvanceMutator, +}; +use libafl_targets::{EDGES_MAP, MAX_EDGES_FOUND, libfuzzer_initialize, libfuzzer_test_one_input}; +use mimalloc::MiMalloc; +use rand::{RngCore, SeedableRng as _, rngs::StdRng}; + +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + +#[derive(Parser)] +struct Opt { + #[arg(short, long, default_value = "./crashes")] + objective_dir: PathBuf, + #[arg(short, long, default_value = "./fuzzer_stats.json")] + stats_file: PathBuf, + #[arg(short, long)] + stdout_file: Option, + #[arg(short, long, default_value = "1337")] + broker_port: u16, + #[arg(short, long, default_value = "0", value_parser = Cores::from_cmdline)] + cores: Cores, +} + +/// The main fn, `no_mangle` as it is a C main +#[unsafe(no_mangle)] +#[expect(clippy::missing_panics_doc, clippy::too_many_lines)] +pub extern "C" fn libafl_main() { + let opt = Opt::parse(); + + let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); + + let monitor = tuple_list!( + OnDiskJsonMonitor::new(opt.stats_file, |_| true), + MultiMonitor::new(|s| println!("{s}")) + ); + + let mut run_client = |state: Option<_>, + mut restarting_mgr: LlmpRestartingEventManager<_, _, _, _, _>, + _client_description| { + let objective_dir = opt.objective_dir.clone(); + + #[allow(static_mut_refs)] // only a problem on nightly + let edges_observer = unsafe { + HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( + "edges", + EDGES_MAP.as_mut_ptr(), + MAX_EDGES_FOUND, + )) + .track_indices() + }; + + let time_observer = TimeObserver::new("time"); + let map_feedback = MaxMapFeedback::new(&edges_observer); + // let kpath_feedback = KPathFeedback::new(nonzero!(nonterminal_start::DISCRIMINANT)); + let calibration = CalibrationStage::new(&map_feedback); + + let mut feedback = feedback_or!( + map_feedback, + // kpath_feedback, + TimeFeedback::new(&time_observer) + ); + + let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + + let mut state = state.unwrap_or_else(|| { + StdState::new( + StdRand::new(), + InMemoryCorpus::new(), + OnDiskCorpus::new(objective_dir).unwrap(), + &mut feedback, + &mut objective, + ) + .unwrap() + }); + + let mutator = AdvanceMutator::new( + StdRng::seed_from_u64(state.rand_mut().next_u64()), + (), + "AdvanceMutator", + ); + + let mutational_stage = StdMutationalStage::new(mutator); + + let mut stages = tuple_list!(calibration, mutational_stage); + + let scheduler = StdScheduler::new(); + + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + let mut harness = |input: &DerivationTree| { + let linearized = WriteVisitor::new(Vec::new()) + .visit(input.node(), 0) + .unwrap() + .continue_value() + .unwrap() + .output(); + + unsafe { + libfuzzer_test_one_input(&linearized); + } + ExitKind::Ok + }; + + // Create the executor for an in-process function with one observer for edge coverage and one for the execution time + let mut executor = InProcessExecutor::with_timeout( + &mut harness, + tuple_list!(edges_observer, time_observer), + &mut fuzzer, + &mut state, + &mut restarting_mgr, + Duration::new(10, 0), + )?; + + let args: Vec = env::args().collect(); + if unsafe { libfuzzer_initialize(&args) } == -1 { + println!("Warning: LLVMFuzzerInitialize failed with -1"); + } + + if state.must_load_initial_inputs() { + let sampler = StdRng::seed_from_u64(state.rand_mut().next_u64()); + let mut generator = FandangoGenerator::new(sampler, ()); + state.generate_initial_inputs( + &mut fuzzer, + &mut executor, + &mut generator, + &mut restarting_mgr, + 10, + )?; + } + + let iters = 1_000_000; + fuzzer.fuzz_loop_for( + &mut stages, + &mut executor, + &mut state, + &mut restarting_mgr, + iters, + )?; + + restarting_mgr.on_restart(&mut state)?; + + Ok(()) + }; + + match Launcher::builder() + .shmem_provider(shmem_provider) + .configuration(EventConfig::from_name("default")) + .monitor(monitor) + .run_client(&mut run_client) + .cores(&opt.cores) + .broker_port(opt.broker_port) + .stdout_file(opt.stdout_file.as_deref()) + .build() + .launch() + { + Ok(()) => (), + Err(Error::ShuttingDown) => println!("Fuzzing stopped by user. Goodbye."), + Err(err) => panic!("Failed to run launcher: {err:?}"), + } +} diff --git a/lm3s6965-demo/.cargo/config.toml b/demos/lm3s6965-demo/.cargo/config.toml similarity index 100% rename from lm3s6965-demo/.cargo/config.toml rename to demos/lm3s6965-demo/.cargo/config.toml diff --git a/lm3s6965-demo/Cargo.lock b/demos/lm3s6965-demo/Cargo.lock similarity index 100% rename from lm3s6965-demo/Cargo.lock rename to demos/lm3s6965-demo/Cargo.lock diff --git a/lm3s6965-demo/Cargo.toml b/demos/lm3s6965-demo/Cargo.toml similarity index 84% rename from lm3s6965-demo/Cargo.toml rename to demos/lm3s6965-demo/Cargo.toml index 6886d51..5435dfd 100644 --- a/lm3s6965-demo/Cargo.toml +++ b/demos/lm3s6965-demo/Cargo.toml @@ -10,9 +10,9 @@ cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rtic = "1.1.3" cortex-m-semihosting = { version = "0.5.0" } embedded-alloc = { git = "https://github.com/rust-embedded/embedded-alloc.git", rev = "89cb8d50e6634130302cd444b3f547aed0fd32dc" } -fandango = { path = ".." } -fandango-runtime = { path = "../runtime" } -fandango-targets = { path = "../targets", features = ["xml"] } +fandango = { path = "../.." } +fandango-runtime = { path = "../../runtime" } +fandango-targets = { path = "../../targets", features = ["xml"] } lm3s6965 = { git = "https://github.com/japaric/lm3s6965.git", rev = "facf63aa0169c773175a143f6014a1d0977fb74f" } panic-semihosting = { version = "0.6.0", features = ["exit"] } rand = { version = "0.9.0", default-features = false, features = ["std_rng"] } @@ -29,4 +29,4 @@ strip = true lto = true [workspace] -members = ["."] \ No newline at end of file +members = [""] \ No newline at end of file diff --git a/lm3s6965-demo/src/main.rs b/demos/lm3s6965-demo/src/main.rs similarity index 100% rename from lm3s6965-demo/src/main.rs rename to demos/lm3s6965-demo/src/main.rs diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 1bd40d7..11e5334 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -10,6 +10,7 @@ std = ["libafl/std", "libafl_bolts/std", "libafl_bolts/serdeany_autoreg"] [dependencies] fandango = { path = ".." } +fandango-runtime = { path = "../runtime" } libafl = { workspace = true, features = ["rand_trait"] } libafl_bolts = { workspace = true, features = ["rand_trait"] } @@ -18,6 +19,8 @@ serde = { workspace = true, features = ["alloc", "derive"] } [dev-dependencies] rand = { workspace = true, features = ["std_rng"] } +fandango-targets = { path = "../targets", features = ["xml", "serde"] } +num-rational = { workspace = true } [lints] workspace = true diff --git a/libafl/src/generator.rs b/libafl/src/generator.rs new file mode 100644 index 0000000..ea405dc --- /dev/null +++ b/libafl/src/generator.rs @@ -0,0 +1,62 @@ +use fandango::generation::Generated; +use libafl::{generators::Generator, state::HasRand}; + +use crate::inputs::DerivationTree; + +/// Generator for `DerivationTree`s. +pub struct FandangoGenerator { + sampler: SM, + generator: G, +} + +impl FandangoGenerator { + /// Creates a new `FandangoGenerator` + #[must_use] + pub fn new(sampler: SM, generator: G) -> Self { + Self { sampler, generator } + } +} + +impl Generator, S> for FandangoGenerator +where + N: Generated, + S: HasRand, +{ + fn generate(&mut self, _state: &mut S) -> Result, libafl::Error> { + Ok(DerivationTree::new(N::generate( + &mut self.sampler, + &mut self.generator, + 0, + ))) + } +} + +#[cfg(test)] +mod test { + use fandango::Fandango; + use libafl::{corpus::NopCorpus, generators::Generator, state::StdState}; + use libafl_bolts::rands::StdRand; + use rand::{SeedableRng as _, rngs::StdRng}; + + use crate::{generator::FandangoGenerator, inputs::DerivationTree}; + + #[derive(Fandango)] + #[fandango(grammar = "../tests/grammars/simple.fan", parse = false, serde = true)] + #[allow(dead_code)] + struct Simple; + + #[test] + fn generator_works() { + let sampler = StdRng::from_seed([0; 32]); + let mut generator = FandangoGenerator::new(sampler, ()); + let mut state = StdState::<_, DerivationTree, _, _>::new( + StdRand::new(), + NopCorpus::new(), + NopCorpus::new(), + &mut (), + &mut (), + ) + .unwrap(); + let _generated: DerivationTree = generator.generate(&mut state).unwrap(); + } +} diff --git a/libafl/src/inputs.rs b/libafl/src/inputs.rs index 8ac6177..67a3bc6 100644 --- a/libafl/src/inputs.rs +++ b/libafl/src/inputs.rs @@ -2,11 +2,10 @@ use core::convert::Infallible; use core::fmt::Debug; use core::hash::{Hash, Hasher}; use core::marker::PhantomData; -use core::num::NonZeroUsize; use core::ops::ControlFlow; use fandango::visitor::kpath::KPathVisitor; +use fandango_runtime::evolvers::basic::AsNodeRef; use libafl::inputs::Input; -use libafl_bolts::impl_serdeany; use libafl_bolts::simd::{MaxReducer, MinReducer, Reducer}; use mappable_rc::Mrc; use serde::{Deserialize, Serialize}; @@ -44,27 +43,12 @@ impl Input for DerivationTree where { } -#[derive(Copy, Clone, Debug, Deserialize, Serialize)] -pub struct NodeCountMetadata { - count: NonZeroUsize, -} - -impl NodeCountMetadata { - pub fn count(&self) -> NonZeroUsize { - self.count - } - - pub fn count_mut(&mut self) -> &mut NonZeroUsize { - &mut self.count - } - - pub fn new(count: NonZeroUsize) -> Self { - Self { count } +impl AsNodeRef for DerivationTree { + fn as_node_ref(&self) -> &N { + &self.node } } -impl_serdeany!(NodeCountMetadata); - pub struct KPathReducer { value: usize, phantom: PhantomData, diff --git a/libafl/src/lib.rs b/libafl/src/lib.rs index 2ec41df..c85ed62 100644 --- a/libafl/src/lib.rs +++ b/libafl/src/lib.rs @@ -3,6 +3,8 @@ extern crate alloc; pub mod feedbacks; +pub mod generator; pub mod inputs; pub mod mutators; pub mod schedulers; +pub mod stage; diff --git a/libafl/src/mutators.rs b/libafl/src/mutators.rs index a153a02..1782d78 100644 --- a/libafl/src/mutators.rs +++ b/libafl/src/mutators.rs @@ -1,7 +1,7 @@ -use crate::inputs::{DerivationTree, NodeCountMetadata}; +use crate::inputs::DerivationTree; use alloc::borrow::Cow; -use alloc::boxed::Box; -use core::num::NonZeroUsize; +use alloc::fmt::Debug; +use alloc::format; use core::ops::DerefMut; use fandango::generation::{InPlaceGenerated, Sampler}; use fandango::typing::{AsNodeMut, Node}; @@ -38,7 +38,7 @@ impl Named for AdvanceMutator { impl Mutator, S> for AdvanceMutator where - N: Node, + N: Node + Debug, // boilerplate for CountNodes and Advance for<'a> N::TypeMut<'a>: InPlaceGenerated, SM: Sampler, @@ -52,25 +52,20 @@ where let seed = state.rand_mut().next(); self.sampler.reseed(seed); - let metadata = state.metadata_map_mut().get_or_insert_with_boxed(|| { - Box::new(NodeCountMetadata::new( - NonZeroUsize::new(input.node_mut().count_nodes()).unwrap(), - )) - }); - let position = self.sampler.sample() % metadata.count(); + let count = input.node().count_nodes(); + let position = self.sampler.sample() % count; let mut node = input.node_mut(); let mut selected = Advance::forward_ref(position) .visit_mut(node.deref_mut(), 0) .unwrap() .break_value() - .unwrap(); + .ok_or_else(|| { + Error::unknown(format!("Failed to advance node at position {position}")) + })?; selected.generate_in_place(&mut self.sampler, &mut self.generator, 0); drop(selected); - let count = metadata.count_mut(); - *count = NonZeroUsize::new(node.count_nodes()).unwrap(); - Ok(MutationResult::Mutated) } @@ -82,18 +77,19 @@ where #[cfg(test)] mod test { + use crate::generator::FandangoGenerator; use crate::inputs::DerivationTree; use crate::mutators::AdvanceMutator; use alloc::boxed::Box; use core::error::Error; use fandango::Fandango; - use fandango::generation::Generated; use libafl::corpus::NopCorpus; + use libafl::generators::Generator as _; use libafl::mutators::Mutator; - use libafl::state::StdState; + use libafl::state::{HasRand as _, StdState}; use libafl_bolts::rands::StdRand; - use rand::SeedableRng; use rand::rngs::StdRng; + use rand::{RngCore as _, SeedableRng}; #[derive(Fandango)] #[fandango(grammar = "../tests/grammars/simple.fan", parse = false, serde = true)] @@ -110,14 +106,15 @@ mod test { &mut (), )?; - let mut rng = StdRng::seed_from_u64(0); + let sampler = StdRng::seed_from_u64(state.rand_mut().next_u64()); - let input = nonterminal_start::generate(&mut rng, &mut (), 0); - let mut input = DerivationTree::new(input); + let mut generator = FandangoGenerator::new(sampler, ()); - let mut mutator = AdvanceMutator::new(rng, (), "test"); + let sampler = StdRng::seed_from_u64(state.rand_mut().next_u64()); + let mut mutator = AdvanceMutator::new(sampler, (), "test"); for _ in 0..10_000 { + let mut input: DerivationTree = generator.generate(&mut state)?; mutator.mutate(&mut state, &mut input)?; } diff --git a/libafl/src/schedulers.rs b/libafl/src/schedulers.rs index c641aac..88a19f5 100644 --- a/libafl/src/schedulers.rs +++ b/libafl/src/schedulers.rs @@ -1,7 +1,7 @@ use crate::feedbacks::GlobalKPathMetadata; use crate::inputs::{DerivationTree, KPathReducer}; use alloc::collections::{BTreeMap, VecDeque}; -use core::ops::{Deref, DerefMut}; +use core::ops::Deref; use fandango::typing::Node; use fandango::visitor::Visitor; use fandango::visitor::kpath::KPathVisit; diff --git a/libafl/src/stage.rs b/libafl/src/stage.rs new file mode 100644 index 0000000..45af653 --- /dev/null +++ b/libafl/src/stage.rs @@ -0,0 +1,190 @@ +use core::{fmt::Debug, marker::PhantomData}; + +use alloc::{format, vec::Vec}; +use fandango::typing::Node; +use fandango_runtime::evolvers::PopulationEvaluator; +use libafl::{ + Error, Evaluator, + mutators::Mutator, + stages::{Restartable, Stage}, + state::{HasCurrentTestcase, HasRand}, +}; + +use crate::inputs::DerivationTree; + +pub struct FandangoEvolutionStage { + phantom: PhantomData, + mutator: MT, + evolver: EV, + generator: G, + sampler: SM, + population_size_from_mutation: usize, + evolution_steps: usize, +} + +impl FandangoEvolutionStage { + pub fn new( + mutator: MT, + evolver: EV, + generator: G, + sampler: SM, + population_size_from_mutation: usize, + evolution_steps: usize, + ) -> Self { + Self { + phantom: PhantomData, + mutator, + evolver, + generator, + sampler, + population_size_from_mutation, + evolution_steps, + } + } +} + +impl Stage for FandangoEvolutionStage +where + S: HasRand + HasCurrentTestcase>, + MT: Mutator, S>, + Z: Evaluator, S>, + EV: PopulationEvaluator>, + EV::Error: Debug, + N: Clone + Node, +{ + fn perform( + &mut self, + fuzzer: &mut Z, + executor: &mut E, + state: &mut S, + manager: &mut EM, + ) -> Result<(), libafl::Error> { + let testcase = state.current_testcase_mut()?; + let input = testcase + .input() + .clone() + .ok_or_else(|| Error::unknown("No input found"))?; + drop(testcase); + + let mutated_population = (0..self.population_size_from_mutation) + .map(|_| { + let mut input = input.clone(); + self.mutator.mutate(state, &mut input)?; + Ok(input) + }) + .collect::, Error>>()?; + + let mut population = self + .evolver + .evaluate_population(&mutated_population) + .map_err(|e| Error::unknown(format!("Failed to evaluate population: {e:?}")))?; + + for _ in 0..self.evolution_steps { + population = self + .evolver + .step(&mut self.generator, &mut self.sampler, population) + .map_err(|e| Error::unknown(format!("Failed to evolve population: {e:?}")))?; + } + + for mutated in mutated_population { + let (_, corpus_id) = fuzzer.evaluate_filtered(state, executor, manager, &mutated)?; + } + + Ok(()) + } +} + +impl Restartable for FandangoEvolutionStage { + fn should_restart(&mut self, state: &mut S) -> Result { + Ok(true) + } + + fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { + Ok(()) + } +} + +#[cfg(test)] +mod test { + use core::time::Duration; + + use fandango::tuple_list::tuple_list; + use fandango_runtime::{evolvers::basic::BasicEvolver, measurement::ViolationFitness}; + use fandango_targets::xml; + use libafl::{ + Fuzzer as _, StdFuzzer, + corpus::{InMemoryCorpus, NopCorpus}, + events::SimpleEventManager, + executors::{ExitKind, nop::NopExecutor}, + feedbacks::ConstFeedback, + monitors::SimpleMonitor, + mutators::HavocScheduledMutator, + schedulers::StdScheduler, + stages::Stage, + state::{HasRand, StdState}, + }; + use libafl_bolts::rands::StdRand; + use num_rational::Ratio; + use rand::{RngCore as _, SeedableRng, rngs::StdRng}; + + use crate::{ + generator::FandangoGenerator, inputs::DerivationTree, mutators::AdvanceMutator, + stage::FandangoEvolutionStage, + }; + + #[test] + #[expect(clippy::similar_names)] + fn test_fandango_evolution_stage() { + let mut feedback = ConstFeedback::new(true); + + let mut state = StdState::new( + StdRand::new(), + InMemoryCorpus::>::new(), + NopCorpus::new(), + &mut feedback, + &mut (), + ) + .unwrap(); + + let sampler = StdRng::seed_from_u64(state.rand_mut().next_u64()); + let mut generator = FandangoGenerator::new(sampler, ()); + let fitness = ViolationFitness::::new(); + + let fixer = (); + let evolver = BasicEvolver::new::( + fitness, + fixer, + 100, + 10, + 1000, + Ratio::new(50, 100), + ) + .expect("Should be valid."); + + let sampler = StdRng::seed_from_u64(state.rand_mut().next_u64()); + + let mutator = + HavocScheduledMutator::new(tuple_list!(AdvanceMutator::new(sampler, (), "test"))); + + let sampler = StdRng::seed_from_u64(state.rand_mut().next_u64()); + let stage = FandangoEvolutionStage::new(mutator, evolver, (), sampler, 100, 10); + + let mut executor = NopExecutor::new(ExitKind::Ok, Duration::from_micros(10), ()); + let mut manager = SimpleEventManager::new(SimpleMonitor::new(|_| {})); + let scheduler = StdScheduler::new(); + + let mut fuzzer = StdFuzzer::new(scheduler, feedback, ()); + + state + .generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut manager, 1) + .unwrap(); + fuzzer + .fuzz_one( + &mut tuple_list!(stage), + &mut executor, + &mut state, + &mut manager, + ) + .unwrap(); + } +} diff --git a/runtime/src/evolvers.rs b/runtime/src/evolvers.rs index eb088d0..d041d62 100644 --- a/runtime/src/evolvers.rs +++ b/runtime/src/evolvers.rs @@ -15,7 +15,6 @@ pub trait Evolver { /// /// Note that you do not need to use the same generators or sampler as on the [`Evolver::step`] /// function. - fn initial( &mut self, generators: &mut G, @@ -26,7 +25,6 @@ pub trait Evolver { /// /// Note that it is also not necessary to use the same generator and sampler every time, but /// changing them may have unexpected impacts on the evolution strategy. - fn step( &mut self, generators: &mut G, @@ -34,3 +32,7 @@ pub trait Evolver { population: Self::Population, ) -> Result; } + +pub trait PopulationEvaluator: Evolver { + fn evaluate_population(&mut self, to_evaluate: &[I]) -> Result; +} diff --git a/runtime/src/evolvers/basic.rs b/runtime/src/evolvers/basic.rs index a9e44e6..60fbd55 100644 --- a/runtime/src/evolvers/basic.rs +++ b/runtime/src/evolvers/basic.rs @@ -1,6 +1,6 @@ //! Basic evolution strategy implementation -use crate::evolvers::Evolver; +use crate::evolvers::{Evolver, PopulationEvaluator}; use crate::measurement::{FitnessMeasurer, HasFitness, HasMeasurement, HasViolations}; use crate::operators::{MutatorVisitor, crossover}; use crate::population::Individual; @@ -235,6 +235,38 @@ where } } +pub trait AsNodeRef { + fn as_node_ref(&self) -> &N; +} + +impl PopulationEvaluator for BasicEvolver +where + H: BasicHook, + N: Node + Generated, + for<'a, 'b, 'c> N::TypeMut<'a>: VisitWithMut> + + InPlaceGenerated + + VisitableChildrenMut>, + for<'a> M: FitnessMeasurer<'a, N, Error = Error, Measurement = V>, + V: HasFitness + HasViolations, + V::Fitness: Ord, + S: Sampler, + I: AsNodeRef, +{ + fn evaluate_population(&mut self, to_evaluate: &[I]) -> Result { + let mut res = to_evaluate + .iter() + .map(|i| { + let node = i.as_node_ref(); + let measurement = self.measurer.evaluate(node)?; + Ok(BasicIndividual::new(node.clone(), measurement)) + }) + .collect::, Self::Error>>()?; + + res.sort_by(|n1, n2| n2.measurement.fitness().cmp(n1.measurement.fitness())); + Ok(res) + } +} + /// A hook which allows tampering with inputs during the evolution pub trait BasicHook { /// Hook which is executed immediately after an individual is created, but before it is diff --git a/targets/Cargo.toml b/targets/Cargo.toml index ee950a2..a619850 100644 --- a/targets/Cargo.toml +++ b/targets/Cargo.toml @@ -15,6 +15,7 @@ xml = [] clang = [] static_defs = [] +serde = ["dep:serde"] [dev-dependencies] num-rational.workspace = true @@ -29,6 +30,7 @@ anyhow.workspace = true embedded-io.workspace = true hashbrown.workspace = true num-rational.workspace = true +serde = { workspace = true, optional = true } [lints] workspace = true diff --git a/targets/src/clang.rs b/targets/src/clang.rs index 019efe7..9c2b5fa 100644 --- a/targets/src/clang.rs +++ b/targets/src/clang.rs @@ -45,7 +45,14 @@ mod defs { /// Base for the C language grammar stored in c_lang.fan. #[derive(Fandango)] - #[fandango(grammar = "grammars/c_lang.fan", parse = false)] + #[cfg_attr( + feature = "serde", + fandango(grammar = "grammars/c_lang.fan", parse = false, serde = true) + )] + #[cfg_attr( + not(feature = "serde"), + fandango(grammar = "grammars/c_lang.fan", parse = false, serde = false) + )] pub struct CLang(Infallible); // Helpful definitions. diff --git a/targets/src/csv.rs b/targets/src/csv.rs index 02d9b29..3bde83b 100644 --- a/targets/src/csv.rs +++ b/targets/src/csv.rs @@ -41,7 +41,14 @@ mod defs { /// Base for the CSV grammar stored in csv.fan. #[derive(Fandango)] - #[fandango(grammar = "grammars/csv.fan", parse = false)] + #[cfg_attr( + feature = "serde", + fandango(grammar = "grammars/csv.fan", parse = false, serde = true) + )] + #[cfg_attr( + not(feature = "serde"), + fandango(grammar = "grammars/csv.fan", parse = false, serde = false) + )] pub struct Csv(Infallible); /// A visitor which collects the violations of the constraints in the CSV grammar. diff --git a/targets/src/rest.rs b/targets/src/rest.rs index a154914..75a20a5 100644 --- a/targets/src/rest.rs +++ b/targets/src/rest.rs @@ -51,7 +51,14 @@ mod defs { /// Base for the REST grammar stored in rest.fan. #[derive(Fandango)] - #[fandango(grammar = "grammars/rest.fan", parse = false)] + #[cfg_attr( + feature = "serde", + fandango(grammar = "grammars/rest.fan", parse = false, serde = true) + )] + #[cfg_attr( + not(feature = "serde"), + fandango(grammar = "grammars/rest.fan", parse = false, serde = false) + )] pub struct Rest(Infallible); /// A visitor which collects the violations of the constraints in the REST grammar. diff --git a/targets/src/scriptsizec.rs b/targets/src/scriptsizec.rs index 08d16ca..cd19692 100644 --- a/targets/src/scriptsizec.rs +++ b/targets/src/scriptsizec.rs @@ -40,7 +40,14 @@ mod defs { /// Base for the ScriptSizeC grammar stored in ssc.fan. #[derive(Fandango)] - #[fandango(grammar = "grammars/scriptsizec.fan", parse = false)] + #[cfg_attr( + feature = "serde", + fandango(grammar = "grammars/scriptsizec.fan", parse = false, serde = true) + )] + #[cfg_attr( + not(feature = "serde"), + fandango(grammar = "grammars/scriptsizec.fan", parse = false, serde = false) + )] pub struct ScriptSizeC(Infallible); /// A visitor which collects the violations of the constraints in the ScriptSizeC grammar. diff --git a/targets/src/xml.rs b/targets/src/xml.rs index 1a7cb32..dff4334 100644 --- a/targets/src/xml.rs +++ b/targets/src/xml.rs @@ -45,7 +45,14 @@ mod defs { /// Base for the XML grammar stored in xml.fan. #[derive(Fandango)] - #[fandango(grammar = "grammars/xml.fan", parse = false)] + #[cfg_attr( + feature = "serde", + fandango(grammar = "grammars/xml.fan", parse = false, serde = true) + )] + #[cfg_attr( + not(feature = "serde"), + fandango(grammar = "grammars/xml.fan", parse = false, serde = false) + )] pub struct Xml(Infallible); /// A visitor which collects the violations of the constraints in the XML grammar.