#40731 [BC-Medium] A malicious signer can force a panic in the coordinator by sending `DkgFailure::BadPrivateShares` with an invalid signer ID
Submitted on Mar 2nd 2025 at 12:12:26 UTC by @christ0s for Attackathon | Stacks II
Report ID: #40731
Report Type: Blockchain/DLT
Report severity: Medium
Target: https://github.com/stacks-network/sbtc/blob/immunefi_attackaton_1.0/Cargo.toml#L31
Impacts:
Network not being able to confirm new transactions (total network shutdown)
Description
Brief/Intro
A malicious signer can cause the coordinator to panic by submitting a DkgFailure::BadPrivateShares message that contains references to non-existent signer IDs. The panic happens because the coordinator attempts to access entries in its internal maps without first validating that the keys exist. This vulnerability allows any malicious signer to completely halt the DKG process, preventing the formation of new signer sets and effectively stopping the network from processing transactions.
Vulnerability Details
In the coordinator's state machine, when processing a DkgStatus::Failure with BadPrivateShares, the code attempts to directly access internal maps with the reported signer IDs without first checking if those IDs actually exist:
// in wsts/src/state_machine/coordinator/fire.rs
DkgFailure::BadPrivateShares(bad_shares) => {
// bad_shares is a map of signer_id to BadPrivateShare
for (bad_signer_id, bad_private_share) in bad_shares {
// ... code that attempts verification ...
// This line causes a panic if bad_signer_id doesn't exist in the map
let dkg_public_shares = &self.dkg_public_shares[bad_signer_id]
.comms
.iter()
.cloned()
.collect::<HashMap<u32, PolyCommitment>>();
// This also causes a panic if bad_signer_id doesn't exist
// uses direct access and not .get()
let dkg_private_shares = &self.dkg_private_shares[bad_signer_id];
// ... rest of verification code ...
}
}Proof of Concept
Proof of Concept
You can add the following code in the transactions_signer.rs in WstsNetMessage::DkgEndBegin(request) => { and run the following test:RUST_BACKTRACE=FULL RUST_LOG=wsts=trace,debug cargo test --package signer --test integration -- transaction_coordinator::sign_bitcoin_transaction --exact --show-output --nocapture
Then we can see the logs:
Was this helpful?