#43307 [BC-High] Not verifying the signatures upon execution leads to direct loss of funds
Submitted on Apr 4th 2025 at 13:05:49 UTC by @HollaDieWaldfee for Attackathon | Movement Labs
Report ID: #43307
Report Type: Blockchain/DLT
Report severity: High
Target: https://github.com/immunefi-team/attackathon-movement/tree/main/networks/movement/movement-full-node
Impacts:
Direct loss of funds
Description
Brief/Intro
Upon execution of a block, every transaction is treated as valid since there is no verification that the signature is valid. Hence, an attacker could send any transaction directly to the DA light node with an invalid signature.
Vulnerability Details
When submitting a transaction, it is verified that the signature is valid. Therefore, every transaction is treated as a valid transaction with a verified signature after it is submitted. However, an attacker could send transactions directly to the DA light node with an invalid signature. This transaction will then be treated like a valid transaction with a verified signature as can be seen in the code snippet below.
async fn execute_block(
&mut self,
block: Block,
block_timestamp: u64,
) -> anyhow::Result<BlockCommitment> {
let block_id = block.id();
let block_hash = HashValue::from_slice(block.id())?;
// get the transactions
let mut block_transactions = Vec::new();
let block_metadata = self.executor.build_block_metadata(
HashValue::sha3_256_of(block_id.as_bytes().as_slice()),
block_timestamp,
)?;
let block_metadata_transaction =
SignatureVerifiedTransaction::Valid(Transaction::BlockMetadata(block_metadata));
block_transactions.push(block_metadata_transaction);
for transaction in block.transactions() {
let signed_transaction: SignedTransaction = bcs::from_bytes(transaction.data())?;
// check if the transaction has already been executed to prevent replays
if self
.executor
.has_executed_transaction_opt(signed_transaction.committed_hash())?
{
continue;
}
@> let signature_verified_transaction = SignatureVerifiedTransaction::Valid(
Transaction::UserTransaction(signed_transaction),
);
block_transactions.push(signature_verified_transaction);
}
// ..
}
On the other hand, Aptos Core verifies the signatures upon execution (reference (3)):
async fn prepare_block(
execute_block_tx: mpsc::UnboundedSender<ExecuteBlockCommand>,
command: PrepareBlockCommand,
) {
// ..
let sig_verified_txns: Vec<SignatureVerifiedTransaction> =
SIG_VERIFY_POOL.install(|| {
let num_txns = txns_to_execute.len();
txns_to_execute
.into_par_iter()
.with_min_len(optimal_min_len(num_txns, 32))
.map(|t| t.into())
.collect::<Vec<_>>()
});
// ..
}
Furthermore, Aptos Core only executes valid transactions and discards invalid transactions (reference (4)):
pub fn execute_single_transaction(
&self,
txn: &SignatureVerifiedTransaction,
resolver: &impl AptosMoveResolver,
log_context: &AdapterLogSchema,
) -> Result<(VMStatus, VMOutput), VMStatus> {
assert!(!self.is_simulation, "VM has to be created for execution");
if let SignatureVerifiedTransaction::Invalid(_) = txn {
let vm_status = VMStatus::error(StatusCode::INVALID_SIGNATURE, None);
let discarded_output = discarded_output(vm_status.status_code());
return Ok((vm_status, discarded_output));
}
// ..
}
To fix this issue, there must be verification in execute_settle.rs#execute_block() that the signature is valid before executing a block. The verification could look like reference (3 & 4) that is made inside of Aptos Core. The way in which Movement integrates with the Aptos Core block execution is invalid since it lacks the crucial signature validation.
Impact Details
Since an attacker can send any transaction directly to the DA light node and that transaction is going to be treated like a valid transaction without signature validation, this causes a direct loss of funds.
References
(1): https://github.com/movementlabsxyz/movement/blob/ec71271bbd022e89a1e3e917629b83442ac2e9d4/protocol-units/da/movement/protocol/light-node/src/passthrough.rs#L230-L248
(2): https://github.com/immunefi-team/attackathon-movement/blob/a2790c6ac17b7cf02a69aea172c2b38d2be8ce00/networks/movement/movement-full-node/src/node/tasks/execute_settle.rs#L246-L248
(3): https://github.com/immunefi-team/attackathon-movement-aptos-core/blob/627b4f9e0b63c33746fa5dae6cd672cbee3d8631/consensus/src/execution_pipeline.rs#L129-L136
(4): https://github.com/immunefi-team/attackathon-movement-aptos-core/blob/627b4f9e0b63c33746fa5dae6cd672cbee3d8631/aptos-move/aptos-vm/src/aptos_vm.rs#L2304-L2308
Proof of Concept
Proof of Concept
An attacker sends a transaction directly to the DA light node by calling
passthrough::batch_write()
(reference (1)).This transaction is going to be treated as valid since there is no verification for the signature upon execution (reference (2)). The transaction is treated as a
SignatureVerifiedTransaction::Valid
type without actually verifying the signature.
Was this helpful?