#38270 [BC-Medium] A signer can send a large number of junk `WstsNetMessage::NonceRequest` through P

Submitted on Dec 29th 2024 at 18:28:23 UTC by @f4lc0n for Attackathon | Stacks

  • Report ID: #38270

  • Report Type: Blockchain/DLT

  • Report severity: Medium

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

  • Impacts:

    • Permanent freezing of funds (fix requires hardfork)

    • API crash preventing correct processing of deposits

Description

Brief/Intro

Each time a signer receives a WstsNetMessage::NonceRequest message, it inserts the msg.txid into the wsts_state_machines. Then a malicious signer can send a large number of junk WstsNetMessage::NonceRequest messages to other signers to make the wsts_state_machines of other signers grow indefinitely, causing them to run out of memory.

Vulnerability Details

The signer/src/transaction_signer.rs::handle_wsts_message function is as follows.

            WstsNetMessage::NonceRequest(request) => {
                tracing::info!("handling NonceRequest");
                if !chain_tip_report.sender_is_coordinator {
                    tracing::warn!("received coordinator message from non-coordinator signer");
                    return Ok(());
                }

                let db = self.context.get_storage();
                let sig_hash = &request.message;
                let validation_outcome = Self::validate_bitcoin_sign_request(&db, sig_hash).await;

                let validation_status = match &validation_outcome {
                    Ok(()) => "success",
                    Err(Error::SigHashConversion(_)) => "improper-sighash",
                    Err(Error::UnknownSigHash(_)) => "unknown-sighash",
                    Err(Error::InvalidSigHash(_)) => "invalid-sighash",
                    Err(_) => "unexpected-failure",
                };

                metrics::counter!(
                    Metrics::SignRequestsTotal,
                    "blockchain" => BITCOIN_BLOCKCHAIN,
                    "kind" => "sweep",
                    "status" => validation_status,
                )
                .increment(1);

                if !self.wsts_state_machines.contains_key(&msg.txid) {
                    let (maybe_aggregate_key, _) = self
                        .get_signer_set_and_aggregate_key(bitcoin_chain_tip)
                        .await?;

                    let state_machine = SignerStateMachine::load(
                        &db,
                        maybe_aggregate_key.ok_or(Error::NoDkgShares)?,
                        self.threshold,
                        self.signer_private_key,
                    )
                    .await?;

                    self.wsts_state_machines.insert(msg.txid, state_machine);
                }
                self.relay_message(msg.txid, &msg.inner, bitcoin_chain_tip)
                    .await?;
            }

As long as msg.txid is not contained in self.wsts_state_machines, this function will insert msg.txid into self.wsts_state_machines. Then, the attacker can send a large number of messages with different txid to make the self.wsts_state_machines of other signers grow infinitely.

Impact Details

It can cause signers to crash due to out of memory, which will cause signers to be unable to process deposits and withdrawls. The user's sBTC is frozen until signers manually process withdrawls.

It requires the attacker to be one of the signers, so I believe it is Medium.

References

None

Proof of Concept

Proof of Concept

  1. Base on: https://github.com/stacks-network/sbtc/releases/tag/0.0.9-rc4

  2. Patch signer/src/config/mod.rs, add attacker tag config

  3. Patch signer/src/main.rs, load attacker tag

  4. Patch docker/docker-compose.yml, add attacker tag

  5. Patch signer/src/network/libp2p/event_loop.rs, add attack action

  6. Patch signer/src/transaction_signer.rs, add some log

  7. Run docker

  8. This PoC sets sbtc-signer-3 as an attacker, which will automatically attack other signers. Observe the log and you will find that self.wsts_state_machines of other signers keeps growing

  9. You can observe the memory usage of signer through docker stats sbtc-signer-1 command

Last updated

Was this helpful?