Permanent freezing of funds (fix requires hardfork)
API crash preventing correct processing of deposits
Description
Brief/Intro
The /new_block api of Signer receives each block of Stacks and reads the events in it. It will receive the following events from sbtc-registry contract:
completed-deposit
withdrawal-accept
withdrawal-create
withdrawal-reject
key-rotation
However, the /new_block api does not handle the event of update-protocol-contract emited by the sbtc-registry contract. Once Governance calls sbtc-registry.update-protocol-contract, the /new_block api will throw a ClarityUnexpectedEventTopic error and skip processing the block. If the block contains other events (such as users' withdrawal-create event), all events will be skipped.
Vulnerability Details
The signer/src/api/new_block.rs::new_block_handler code is as follows.
If RegistryEvent::try_new returns an error, the /new_block api will directly return StatusCode::OK and skip processing the Stacks block.
The signer/src/stacks/events.rs::try_new code is as follows.
pub fn try_new(value: ClarityValue, tx_info: TxInfo) -> Result<Self, EventError> {
match value {
ClarityValue::Tuple(TupleData { data_map, .. }) => {
let mut event_map = RawTupleData::new(data_map, tx_info);
// Lucky for us, each sBTC print event in the sbtc-registry
// smart contract has a topic. We use that to match on what
// to expect when decomposing the event from a
// [`ClarityValue`] into a proper type.
let topic = event_map.remove_string("topic")?;
match topic.as_str() {
"completed-deposit" => event_map.completed_deposit(),
"withdrawal-accept" => event_map.withdrawal_accept(),
"withdrawal-create" => event_map.withdrawal_create(),
"withdrawal-reject" => event_map.withdrawal_reject(),
"key-rotation" => event_map.key_rotation(),
_ => Err(EventError::ClarityUnexpectedEventTopic(topic)),
}
}
value => Err(EventError::ClarityUnexpectedValue(value, tx_info)),
}
}
}
If the event topic is update-protocol-contract, it will throw a ClarityUnexpectedValue error.
Then, once Governance calls sbtc-registry.update-protocol-contract and emits an update-protocol-contract, all events of the block will be skipped.
Impact Details
Signer may ignore some events from Stacks. The specific impacts are as follows:
If it not receive a withdrawal-create event, the Signer will not process the user's withdrawal request. The user’s sBTC will be frozen unless the signers manually process the withdrawal.
If it not receive a key-rotation event, the Signer will not receive the new rotate_key. Then the Signer will process the deposits.
Since it freezes the user's funds, but it is temporary, I consider this a Medium.
References
None
Proof of Concept
Proof of Concept
Add this test case into signer/src/api/new_block.rs file.