#38477 [BC-High] A single signer can abort every attempted signing round by providing an invalid pac

Submitted on Jan 4th 2025 at 12:33:00 UTC by @n4nika for Attackathon | Stacks

  • Report ID: #38477

  • Report Type: Blockchain/DLT

  • Report severity: High

  • Target: https://github.com/stacks-network/sbtc/tree/immunefi_attackaton_0.9/signer

  • Impacts:

    • Network not being able to confirm new transactions (total network shutdown)

Description

Summary

Incorrect handling of errors in wsts::process_message and transaction_coordinator::drive_wsts_state_machine, allow a single malicious signer to abort all signing rounds, effectively halting the network since no new transactions can be sent if we cannot form an aggregate signature.

Finding Description

In wsts::process_message, if gather_sig_shares fails, we return a Some value for the tuple's second value:

State::SigShareGather(signature_type) => {
    if let Err(e) = self.gather_sig_shares(packet, signature_type) {
        return Ok((
            None,
            Some(OperationResult::SignError(SignError::Coordinator(e))), // <-----
        ));
    }
    // [...]
}

In transaction_coordinator::drive_wsts_state_machine, we then instantly return:

This means that whenever gather_sig_shares returns an error, we instantly abort the signature round.

Such an error can be triggered easily when we look at that function:

We see that the first check can be directly triggered by specifying an incorrect dkg_id which is taken directly from the signer-sent package.

Mitigation

Now I'm unsure how to best prevent this but I think the actual problem is wsts::process_message returning a tuple which looks like a success message when it encounters an error.

Proof of Concept

PoC

In order to show this, please apply the following diff and execute the test with cargo test --package signer --test integration -- transaction_coordinator::sign_bitcoin_transaction_poc --exact --show-output --ignored --nocapture

This will print the results of the signature requests. Now since in the tests we have a signer set of 2/3, the results differ (more or less requests fail). However, in a real-world scenario with a set of 11/15, we are pretty much guaranteed to be able to send the request before the coordinator finalizes the signing round.

Last updated

Was this helpful?