From 2bcb77b19da9360c678c3c48d54602a4ca96d033 Mon Sep 17 00:00:00 2001 From: tokatoka Date: Mon, 26 Jan 2026 18:56:19 +0100 Subject: [PATCH 1/4] Wrap the secondary client in the proxy script --- fuzzamoto-cli/src/utils/nyx.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/fuzzamoto-cli/src/utils/nyx.rs b/fuzzamoto-cli/src/utils/nyx.rs index f73b59dc..3c1b036c 100644 --- a/fuzzamoto-cli/src/utils/nyx.rs +++ b/fuzzamoto-cli/src/utils/nyx.rs @@ -110,12 +110,26 @@ pub fn create_nyx_script( script.push(format!("echo \"{}\" >> ./bitcoind_proxy", proxy_script)); script.push("chmod +x ./bitcoind_proxy".to_string()); + if secondary_bitcoind.is_some() { + let secondary_proxy_script = format!( + "{} LD_LIBRARY_PATH=/tmp LD_BIND_NOW=1 {} ./{} \\$@", + asan_options, + crash_handler_preload, + secondary_bitcoind.unwrap() + ); + script.push("echo \"#!/bin/sh\" > ./bitcoind2_proxy".to_string()); + script.push(format!( + "echo \"{}\" >> ./bitcoind2_proxy", + secondary_proxy_script + )); + script.push("chmod +x ./bitcoind2_proxy".to_string()); + } + // Run the scenario script.push(format!( - "RUST_LOG=debug LD_LIBRARY_PATH=/tmp LD_BIND_NOW=1 ./{} ./bitcoind_proxy {} ./{} > log.txt 2>&1", + "RUST_LOG=debug LD_LIBRARY_PATH=/tmp LD_BIND_NOW=1 ./{} ./bitcoind_proxy {} ./bitcoind2_proxy > log.txt 2>&1", scenario_name, rpc_path.unwrap_or(""), - secondary_bitcoind.unwrap_or("") )); // Debug info From d6b5d3898317fa8850d7c3edb3409c6359dec3a2 Mon Sep 17 00:00:00 2001 From: tokatoka Date: Mon, 26 Jan 2026 19:25:15 +0100 Subject: [PATCH 2/4] Allow dumping bitcoind log to host --- fuzzamoto-cli/Cargo.toml | 3 +++ fuzzamoto-cli/src/utils/nyx.rs | 19 +++++++++++--- fuzzamoto-scenarios/Cargo.toml | 1 + fuzzamoto-scenarios/bin/ir.rs | 46 +++++++++++++++++++++++++++++++++- 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/fuzzamoto-cli/Cargo.toml b/fuzzamoto-cli/Cargo.toml index c381884c..7a93594e 100644 --- a/fuzzamoto-cli/Cargo.toml +++ b/fuzzamoto-cli/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true [lints] workspace = true +[features] +nyx_log = [] + [dependencies] clap = { version = "4.4", features = ["derive", "string"] } env_logger = "0.11.6" diff --git a/fuzzamoto-cli/src/utils/nyx.rs b/fuzzamoto-cli/src/utils/nyx.rs index 3c1b036c..f8c49525 100644 --- a/fuzzamoto-cli/src/utils/nyx.rs +++ b/fuzzamoto-cli/src/utils/nyx.rs @@ -99,11 +99,21 @@ pub fn create_nyx_script( ] .join(":"); + #[cfg(feature = "nyx_log")] + let primary_log = " > /tmp/primary.log 2>&1"; + #[cfg(not(feature = "nyx_log"))] + let primary_log = ""; + + #[cfg(feature = "nyx_log")] + let secondary_log = " > /tmp/secondary.log 2>&1"; + #[cfg(not(feature = "nyx_log"))] + let secondary_log = ""; + let asan_options = format!("ASAN_OPTIONS={}", asan_options); let crash_handler_preload = format!("LD_PRELOAD=./{}", crash_handler_name); let proxy_script = format!( - "{} LD_LIBRARY_PATH=/tmp LD_BIND_NOW=1 {} ./bitcoind \\$@", - asan_options, crash_handler_preload, + "{} LD_LIBRARY_PATH=/tmp LD_BIND_NOW=1 {} ./bitcoind \\$@{}", + asan_options, crash_handler_preload, primary_log ); script.push("echo \"#!/bin/sh\" > ./bitcoind_proxy".to_string()); @@ -112,10 +122,11 @@ pub fn create_nyx_script( if secondary_bitcoind.is_some() { let secondary_proxy_script = format!( - "{} LD_LIBRARY_PATH=/tmp LD_BIND_NOW=1 {} ./{} \\$@", + "{} LD_LIBRARY_PATH=/tmp LD_BIND_NOW=1 {} ./{} \\$@{}", asan_options, crash_handler_preload, - secondary_bitcoind.unwrap() + secondary_bitcoind.unwrap(), + secondary_log ); script.push("echo \"#!/bin/sh\" > ./bitcoind2_proxy".to_string()); script.push(format!( diff --git a/fuzzamoto-scenarios/Cargo.toml b/fuzzamoto-scenarios/Cargo.toml index 3a0f87fd..95eddf4a 100644 --- a/fuzzamoto-scenarios/Cargo.toml +++ b/fuzzamoto-scenarios/Cargo.toml @@ -9,6 +9,7 @@ repository.workspace = true [features] fuzz = ["compile_in_vm", "force_send_and_ping", "nyx"] reproduce = ["compile_in_vm", "force_send_and_ping", "fuzzamoto/reproduce"] +nyx_log = ["nyx"] nyx = ["dep:fuzzamoto-nyx-sys"] compile_in_vm = [] diff --git a/fuzzamoto-scenarios/bin/ir.rs b/fuzzamoto-scenarios/bin/ir.rs index 7b9bb7cc..05b70d1a 100644 --- a/fuzzamoto-scenarios/bin/ir.rs +++ b/fuzzamoto-scenarios/bin/ir.rs @@ -428,6 +428,45 @@ pub fn probe_recent_block_hashes( return Some(ProbeResult::RecentBlockes { result: result }); } +#[cfg(feature = "nyx_log")] +const PRIMARY_LOG: &str = "/tmp/primary.log"; +#[cfg(all( + feature = "nyx_log", + any(feature = "oracle_netsplit", feature = "oracle_consensus") +))] +const SECONDARY_LOG: &str = "/tmp/secondary.log"; + +#[cfg(feature = "nyx_log")] +fn dump_log_to_host() { + { + let log = std::fs::read(PRIMARY_LOG); + if let Ok(data) = log { + unsafe { + nyx_dump_file_to_host( + PRIMARY_LOG.as_ptr() as *const i8, + PRIMARY_LOG.len(), + data.as_ptr(), + data.len(), + ); + } + } + #[cfg(any(feature = "oracle_netsplit", feature = "oracle_consensus"))] + { + let log = std::fs::read(SECONDARY_LOG); + if let Ok(data) = log { + unsafe { + nyx_dump_file_to_host( + SECONDARY_LOG.as_ptr() as *const i8, + SECONDARY_LOG.len(), + data.as_ptr(), + data.len(), + ); + } + } + } + } +} + impl Scenario<'_, TestCase> for IrScenario where TX: Transport, @@ -472,7 +511,12 @@ where } self.print_received(); - self.evaluate_oracles() + let res = self.evaluate_oracles(); + + #[cfg(feature = "nyx_log")] + dump_log_to_host(); + + res } } From ca952408c64602e58187152d75e5c605a3f05d1d Mon Sep 17 00:00:00 2001 From: tokatoka Date: Tue, 27 Jan 2026 13:47:20 +0100 Subject: [PATCH 3/4] doc: add docs for out-of-VM and in-VM debugging --- doc/src/usage/reproducing.md | 49 ++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/doc/src/usage/reproducing.md b/doc/src/usage/reproducing.md index 9d8c4c0b..43a74cd3 100644 --- a/doc/src/usage/reproducing.md +++ b/doc/src/usage/reproducing.md @@ -15,11 +15,18 @@ inherit stdout from the target application, such that any logs, stack traces, etc. are printed to the terminal. -## `http-server` example +## `http-server` example (Out-of-VM execution) -Run the scneario with the input supplied through `stdin` and pass the right +You can just run the scenario directly to debug the out-of-vm execution of the scenario `bitcoind` binary: +First, compile the scenario with `reproduce` features +``` +cargo build --release -p fuzzamoto-scenarios --features "fuzzamoto/reproduce","force_send_and_ping" +``` + +Then, run the scneario with the input supplied through `stdin` and pass the right + ``` cat ./testcase.dat | RUST_LOG=info ./target/release/scenario-http-server ./bitcoind # Use "echo '' | base64 --decode | ..." if you have the input as a base64 string @@ -31,6 +38,44 @@ Or alternatively using `FUZZAMOTO_INPUT`: FUZZAMOTO_INPUT=$PWD/testcase.dat RUST_LOG=info ./target/release/scenario-http-server ./bitcoind ``` +## `ir` example (In-VM execution) +You can also use `-r` option to debug the in-vm execution of the scenario. + +First, build all the packages with both `fuzz` and `nyx_log` feature. +``` +cd /fuzzamoto +BITCOIND_PATH=/bitcoin/build_fuzz/bin/bitcoind cargo build --workspace --release --features fuzz,nyx_log,inherit_stdout +``` + +`nyx_log` feature is necessary as it allows us to retrieve the log from bitcoind later. +You can also add `inherit_stdout` feature if you want more verbose logs from bitcoind. + +Then, build the crash handler and initialize the nyx share dir as usual: + +``` +# Build the crash handler +clang-19 -fPIC -DENABLE_NYX -D_GNU_SOURCE -DNO_PT_NYX \ + ./fuzzamoto-nyx-sys/src/nyx-crash-handler.c -ldl -I. -shared -o libnyx_crash_handler.so +# Initialize the nyx share dir +./target/release/fuzzamoto-cli init --sharedir /tmp/fuzzamoto_scenario-ir \ + --crash-handler /fuzzamoto/libnyx_crash_handler.so \ + --bitcoind /bitcoin/build_fuzz/bin/bitcoind \ + --scenario ./target/release/scenario-ir \ + --nyx-dir ./target/release/ +``` + +Now, run the fuzzer with `-r` option +``` +RUST_LOG=info ./target/release/fuzzamoto-libafl \ + --input /tmp/in --output /tmp/out/ \ + --share /tmp/fuzzamoto_scenario-ir/ \ + -r \ + --cores 0 --verbose +``` +This will execute the given testcase for only once in "reproduce" mode + +After this you will find the log from the bitcoind in `/tmp/out/workdir/dump/primary.log` + ## Troubleshooting * Make sure to not use the `nyx` feature or else you'll see: From 60bdd0585a64ecd64cb8a24b897d444f1d615c8a Mon Sep 17 00:00:00 2001 From: tokatoka Date: Tue, 27 Jan 2026 15:11:42 +0100 Subject: [PATCH 4/4] lower the number of required testcases for ci to pass --- ci/libafl.justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/libafl.justfile b/ci/libafl.justfile index d74ac940..75045903 100644 --- a/ci/libafl.justfile +++ b/ci/libafl.justfile @@ -23,7 +23,7 @@ clean: test: compile compile_nyx corpus #!/bin/bash timeout 16s sh -c './target/release/fuzzamoto-libafl --input /tmp/in/ --output /tmp/out/ --share /tmp/fuzzamoto_scenario-ir/ --cores 0 --verbose > stdout.log' - if grep -qa "corpus: 30" stdout.log; then + if grep -qa "corpus: 15" stdout.log; then echo "Fuzzer is working" else echo "Fuzzer does not generate enough testcases"