Skip to content

Conversation

R9295
Copy link
Collaborator

@R9295 R9295 commented Sep 1, 2025

This PR introduces a fuzzing harness for pallet-domains, encoding invariants and valid user-processes when fuzzing for maximum impact.

Please note that the changes seem large due to the use of the macro to make functions public conditionally.

Code contributor checklist:

Copy link

🛡️ Immunefi PR Reviews

We noticed that your project isn't set up for automatic code reviews. If you'd like this PR reviewed by the Immunefi team, you can request it manually using the link below:

🔗 Send this PR in for review

Once submitted, we'll take care of assigning a reviewer and follow up here.

@jfrank-summit
Copy link
Member

@R9295 thank you for this PR! It will likely not get fully reviewed until early next week. In the meantime could you please cleanup the commit history. See our contribution guide for what we are expecting. Thanks!

Copy link
Member

@teor2345 teor2345 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this PR, it's an important part of our security testing.

I've made a large number of comments, but most of them are code style nitpicks (starting with Nit:). Feel free to ignore those, one of the developers can do them later.

The most important revisions are:

  • adding comments to new functions and structs, so reviewers know what they're meant to do
  • reducing the size of the PR by doing re-exports in the standard Rust way

use sp_core::H256;
use sp_domains::{DomainId, OperatorId};
use sp_runtime::traits::One;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Most of the crate uses a single import block. This might require a rustfmt run after this change.

Suggested change

.1;
let slash_reason = match slash_reason % 2 {
0 => SlashedReason::InvalidBundle(0),
_ => SlashedReason::BadExecutionReceipt(H256::from([0u8; 32])),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I try to avoid wildcard matches, so the compiler will tell us that we need to update them if the surrounding code changes. What we really want is a compiler error when there is a new SlashedReason.

The standard way of doing it would be:

  • add an enum without fields that matches each existing SlashedReason, and converts to a dummy SlashedReason
  • add a function that converts a u8 to that enum
  • convert from the u8 to the fieldless enum to the dummy SlashedReason here

@teor2345 teor2345 changed the title SRLabs: Intrdouce fuzzing harness for pallet-domains SRLabs: Introduce fuzzing harness for pallet-domains Sep 8, 2025
@jfrank-summit
Copy link
Member

Any update on this PR @R9295?

@R9295
Copy link
Collaborator Author

R9295 commented Oct 6, 2025

@jfrank-summit sorry, I was OOO. On it

cursor[bot]

This comment was marked as outdated.

@R9295 R9295 force-pushed the main branch 2 times, most recently from 01b42f5 to 20420d7 Compare October 6, 2025 13:13
Copy link
Member

@teor2345 teor2345 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good, but I still have some questions about how it works

Comment on lines +96 to +102
fuzz = [
"dep:domain-pallet-executive",
"dep:pallet-timestamp",
"dep:pallet-block-fees",
"dep:sp-externalities",
"dep:sp-keystore",
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the fuzz feature requires std, then please depend on the std feature here. That way you can replace all(feature = "std", feature = "fuzz") with feature = "fuzz".

This makes the cfgs easier to maintain.

Comment on lines +12 to +27
#[cfg(all(not(test), feature = "std", feature = "fuzz"))]
pub mod tests;

pub mod block_tree;
pub mod bundle_storage_fund;
pub mod domain_registry;
pub mod extensions;
#[cfg(feature = "fuzz")]
pub mod fuzz_utils;
pub mod migrations;
mod nominator_position;
pub mod runtime_registry;
pub mod staking;
#[cfg(feature = "fuzz")]
pub mod staking_epoch;
#[cfg(not(feature = "fuzz"))]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please replace these duplicate module declarations with:

#[cfg(feature = "fuzz")]
pub use module;

Only declaring modules once makes the code easier to maintain, and provides clearer compiler errors when we rename a module.

Comment on lines -1 to +22
use crate::block_tree::{BlockTreeNode, verify_execution_receipt};
use crate::domain_registry::{DomainConfig, DomainConfigParams, DomainObject};
use crate::runtime_registry::ScheduledRuntimeUpgrade;
use crate::staking_epoch::do_finalize_domain_current_epoch;
#[cfg(test)]
use crate::block_tree::verify_execution_receipt;
#[cfg(test)]
use crate::domain_registry::{DomainConfig, DomainObject};

#[cfg(test)]
use crate::Config;
#[cfg(test)]
use crate::tests::pallet_mock_version_store::MockPreviousBundleAndExecutionReceiptVersions;
use crate::{self as pallet_domains, BlockSlot, FungibleHoldId};
#[cfg(test)]
use crate::{
self as pallet_domains, BalanceOf, BlockSlot, BlockTree, BlockTreeNodes, BundleError, Config,
ConsensusBlockHash, DomainBlockNumberFor, DomainHashingFor, DomainRegistry,
DomainRuntimeUpgradeRecords, DomainRuntimeUpgrades, ExecutionInbox, ExecutionReceiptOf,
FraudProofError, FungibleHoldId, HeadDomainNumber, HeadReceiptNumber, NextDomainId,
OperatorConfig, RawOrigin as DomainOrigin, RuntimeRegistry, ScheduledRuntimeUpgrades,
BalanceOf, BlockTree, BlockTreeNodes, BundleError, ConsensusBlockHash, DomainBlockNumberFor,
DomainHashingFor, DomainRegistry, DomainRuntimeUpgradeRecords, DomainRuntimeUpgrades,
ExecutionInbox, ExecutionReceiptOf, FraudProofError, HeadDomainNumber, HeadReceiptNumber,
NextDomainId, OperatorConfig, OperatorId, ProofOfElection, RawOrigin as DomainOrigin,
RuntimeId, RuntimeRegistry, ScheduledRuntimeUpgrades, block_tree::BlockTreeNode,
domain_registry::DomainConfigParams, runtime_registry::ScheduledRuntimeUpgrade,
staking_epoch::do_finalize_domain_current_epoch,
};
use core::mem;
use domain_runtime_primitives::BlockNumber as DomainBlockNumber;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a large number of new #[cfg(test)] in this module, which makes it difficult to maintain.

It looks like it would be easier to have two module files, with two different compilation cfg conditions:

  • test_support: cfg(any(test, feature = "fuzz")): all the types and functions you want to use in fuzzing, that are currently used in tests
  • tests: cfg(test): the existing tests (marked with #[test]) and the code that isn't used for fuzzing

Creating a new module and moving shared code into it should be a separate commit, at the start of your PR.


Command to install ziggy:
```
cargo install --force ziggy cargo-afl honggfuzz grcov
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is --force needed here?

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
sudo apt install -y protobuf-compiler binutils-dev
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line only works on a Linux distribution that uses apt packages. Please delete it.

Install instructions and commands that use sudo belong in the README, because they are only run once, and they can be different for different OS distributions or package managers.

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script should include the standard checks that make sure every command runs correctly:

# http://redsymbol.net/articles/unofficial-bash-strict-mode/
set -euo pipefail

Comment on lines +3 to +4
./fuzz/staking/target
./fuzz/staking/output
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still required now that the crate has been moved into the workspace?

@R9295 R9295 force-pushed the main branch 2 times, most recently from 3393a26 to 4f25c3f Compare October 10, 2025 09:17
Copy link
Member

@teor2345 teor2345 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this documentation!

These notes are for whoever next picks up this PR. I've also resolved any completed changes, and un-resolved anything that's still needed.

"domains/test/service",
"domains/test/utils",
"shared/*",
"fuzz/staking",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: alphabetical order

teor2345

This comment was marked as off-topic.

@teor2345
Copy link
Member

bugbot run

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment @cursor review or bugbot run to trigger another review on this PR

teor2345
teor2345 previously approved these changes Oct 10, 2025
Copy link
Member

@teor2345 teor2345 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More future change notes

run: cargo install --force ziggy cargo-afl honggfuzz grcov

- name: test fuzzer
run: scripts/run-fuzzer-ci.sh
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This job should fail if:

  • there are crashes or timeouts
  • there are zero instances, speed, execs, or coverage
  • the status is not "ziggy rocking"

That way we will check and analyse any fuzzing results or run failures.

sudo apt install -y protobuf-compiler binutils-dev

cd ./fuzz/staking && cargo ziggy build --no-honggfuzz
cargo ziggy fuzz --timeout 600 --release
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The timeout should be a parameter (with a default of 5 minutes?) for CI. Making it a parameter lets developers run the script outside CI for long fuzzing sessions.

We can drop the -ci from the script name when we make this change.


cd ./fuzz/staking && cargo ziggy build --no-honggfuzz
# cargo ziggy fuzz doesn't allow us to set a number of runs or a run time limit
timeout 5m cargo ziggy fuzz --release
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
timeout 5m cargo ziggy fuzz --release
timeout --preserve-status 5m cargo ziggy fuzz --release

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently this exits with status 143 due to the SIGTERM sent by timeout.

The script should succeed if the exit status is 143, but fail on any other status (zero indicates the fuzzer has changed behaviour or there is a bug that caused it to exit early, so we should fail on that).

Unless there are plans to add an iteration or time limit to the fuzzer itself?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants