#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?