#41516 [BC-High] The attacker exceeds the number of transactions TOO_NEW_TOLERANCE and performs a DoS attack.

Submitted on Mar 16th 2025 at 05:52:12 UTC by @zhaojie for Attackathon | Movement Labs

  • Report ID: #41516

  • Report Type: Blockchain/DLT

  • Report severity: High

  • Target: https://github.com/immunefi-team/attackathon-movement/tree/main/protocol-units/execution/maptos/opt-executor

  • Impacts:

    • Causing network processing nodes to process transactions from the mempool beyond set parameters

Description

Brief/Intro

When used_sequence_number is 0, an attacker can commit more transactions than TOO_NEW_TOLERANCE, resulting in a DoS attack.

Vulnerability Details

When the sequence_number of the transaction submitted by the user is 0,

The has_invalid_sequence_number function does not set used_sequence_number +1, which will cause used_sequence_number to remain 0 until the transaction is executed:

	fn has_invalid_sequence_number(
		&self,
		transaction: &SignedTransaction,
	) -> Result<SequenceNumberValidity, Error> {
		// check against the used sequence number pool
->		let used_sequence_number = self
			.used_sequence_number_pool
			.get_sequence_number(&transaction.sender())
			.unwrap_or(0);

		// validate against the state view
		let state_view = self.db_reader.latest_state_checkpoint_view().map_err(|e| {
			Error::InternalError(format!("Failed to get latest state view: {:?}", e))
		})?;

		// this checks that the sequence number is too old or too new
		let committed_sequence_number =
			vm_validator::get_account_sequence_number(&state_view, transaction.sender())?;

		debug!(
			"Used sequence number: {:?} Committed sequence number: {:?}",
			used_sequence_number, committed_sequence_number
		);
		let min_used_sequence_number =
->			if used_sequence_number > 0 { used_sequence_number + 1 } else { 0 };

		let min_sequence_number = (min_used_sequence_number).max(committed_sequence_number);

		let max_sequence_number = committed_sequence_number + TOO_NEW_TOLERANCE;
        ......
    }

	async fn submit_transaction(
		&mut self,
		transaction: SignedTransaction,
	) -> Result<SubmissionStatus, Error> {
		......
		// Pre-execute Tx to validate its content.
		// Re-create the validator for each Tx because it uses a frozen version of the ledger.
		let vm_validator = VMValidator::new(Arc::clone(&self.db_reader));
		let tx_result = vm_validator.validate_transaction(transaction.clone())?;
		// invert the application priority with the u64 max minus the score from aptos (which is high to low)
		let application_priority = u64::MAX - tx_result.score(); //@audit 攻击者传入错误的 tx_result.score 会导致循环退出?
		match tx_result.status() {
			Some(_) => {
				let ms = MempoolStatus::new(MempoolStatusCode::VmError);
				debug!("Transaction not accepted: {:?}", tx_result.status());
				return Ok((ms, tx_result.status()));
			}
			None => {
				debug!("Transaction accepted by VM: {:?}", transaction);
			}
		}

		// has_invalid_sequence_number -> committed_sequence_number
		// sequence_number = committed_sequence_number
->		let sequence_number = match self.has_invalid_sequence_number(&transaction)? {
			SequenceNumberValidity::Valid(sequence_number) => sequence_number,
			SequenceNumberValidity::Invalid(status) => {
				return Ok(status);
			}
		};

		// Add the txn for future validation
		debug!("Adding transaction to mempool: {:?} {:?}", transaction, sequence_number);
		let status = self.core_mempool.add_txn(
			transaction.clone(),
			0,
			sequence_number,
			TimelineState::NonQualified,
			true,
		);

		match status.code {
			MempoolStatusCode::Accepted => {
				let now = chrono::Utc::now().timestamp_millis() as u64;
				debug!("Transaction accepted: {:?}", transaction);
				let sender = transaction.sender();
->				let transaction_sequence_number = transaction.sequence_number();
				self.transaction_sender
					.send((application_priority, transaction))
					.await
					.map_err(|e| anyhow::anyhow!("Error sending transaction: {:?}", e))?;
				// increment transactions in flight
				{
					let mut transactions_in_flight = self.transactions_in_flight.write().unwrap();
					transactions_in_flight.increment(now, 1);
				}
				self.core_mempool.commit_transaction(&sender, sequence_number);

				// update the used sequence number pool
				info!(
					"Setting used sequence number for {:?} to {:?}",
					sender, transaction_sequence_number
				);
->				self.used_sequence_number_pool.set_sequence_number(
					&sender,
					transaction_sequence_number,
					now,
				);
			}
			_ => {
				warn!("Transaction not accepted: {:?}", status);
			}
		}

		// report status
		Ok((status, None))
	}
}

https://github.com/immunefi-team/attackathon-movement/blob/a2790c6ac17b7cf02a69aea172c2b38d2be8ce00/protocol-units/execution/maptos/opt-executor/src/background/transaction_pipe.rs#L188

Therefore, until committed_sequence_number is not changed before the transaction is executed, the user can commit the transaction repeatedly with 0 sequence_number, and has_invalid_sequence_number will be validated.

core_mempool.add_txn allows adding transactions with the same sequence_number, the attacker only needs to modify gas_price:

Since the new transaction modifies gas_price, a different transaction.id is generated, and sequence_number duplicate transactions can be written to DA:

Thus, an attacker can submit a large number of invalid transactions, exceeding the number limit of TOO_NEW_TOLERANCE.

And the cost to the attacker is very low, only a small amount of gas needs to pass the verification of the transaction.

Impact Details

Anyone can conduct a DoS attack on the network.

References

https://github.com/immunefi-team/attackathon-movement/blob/a2790c6ac17b7cf02a69aea172c2b38d2be8ce00/protocol-units/execution/maptos/opt-executor/src/background/transaction_pipe.rs#L188

Proof of Concept

Proof of Concept

  1. The attacker deposits a small amount of gas into the new account and verifies the transaction.

  2. The new account sequence_number is 0.

  3. The attacker increases the gas price by one unit and uses the same sequence_number(=0) to generate transaction data in batches.

  4. The attacker submits more transactions than the TOO_NEW_TOLERANCE limit.

Was this helpful?