#41978 [BC-Insight] Values of the current gc_slot can be garbage collected in edge case
Submitted on Mar 19th 2025 at 18:30:52 UTC by @HollaDieWaldfee for Attackathon | Movement Labs
Report ID: #41978
Report Type: Blockchain/DLT
Report severity: Insight
Target: https://github.com/immunefi-team/attackathon-movement/tree/main/util/collections
Impacts:
Causing network processing nodes to process transactions from the mempool beyond set parameters
Description
Brief/Intro
When the transactions in flight are garbage collected, values that have expired should be removed. But in certain cases, this even removes values of the current gc_slot that have not expired yet and have just been added.
Vulnerability Details
Let's take a look at how this happens.
Every time a transaction is received and added to the mempool, the transactions in flight are garbage collected, if enough time has passed after the last time they have been garbage collected. This can be verified by taking a look at reference (1).
Inside of gc()
, all slots that are too old are removed. However, if self.value_ttl.get() / self.gc_slot_duration.get()
rounds down to zero, the current slot will be removed as well even though it has not expired yet.
As a result, values that have not expired will be garbage collected.
In contrast, the garbage collector for the used_sequence_number_pool
correctly only removes slots until the current one. To verify this, please take a look at reference (4). Given this difference to reference (4), the garbage collection for in-flight transactions is clearly flawed and does not meet the security guarantees - that the current slot can't be removed - that the sequence number garbage collector meets.
Impact Details
Values that have not expired will be garbage collected. Hence, the amount of transactions in flight will be lower than it should and more transactions are allowed to be submitted.
References
(1): https://github.com/immunefi-team/attackathon-movement/blob/a2790c6ac17b7cf02a69aea172c2b38d2be8ce00/protocol-units/execution/maptos/opt-executor/src/background/transaction_pipe.rs#L140-L153 (2): https://github.com/immunefi-team/attackathon-movement/blob/a2790c6ac17b7cf02a69aea172c2b38d2be8ce00/util/collections/src/garbage/counted.rs#L70-L71 (3): https://github.com/immunefi-team/attackathon-movement/blob/a2790c6ac17b7cf02a69aea172c2b38d2be8ce00/util/collections/src/garbage/counted.rs#L72-L76 (4): https://github.com/immunefi-team/attackathon-movement/blob/a2790c6ac17b7cf02a69aea172c2b38d2be8ce00/protocol-units/execution/maptos/opt-executor/src/gc_account_sequence_number.rs#L79-L84
Proof of Concept
Proof of Concept
A new transaction is received and successfully submitted.
transactions_in_flight.gc()
is called sinceself.last_gc.elapsed() >= GC_INTERVAL
. Again, this can be verified by taking a look at reference (1).As showcased in reference (2), this function calculates the
slot_cutoff
in the following way:gc_slot.saturating_sub(self.value_ttl.get() / self.gc_slot_duration.get())
. Ifself.value_ttl.get() / self.gc_slot_duration.get()
rounds down to zero, theslot_cutoff
will be the currentgc_slot
. This means that only the slots after the cutoff will be kept and all of the other slots, including the current slot, wll be removed (reference (3)).Now, the current slot will be removed even though it has not expired yet. As a result, the amount of transactions in flight will be decreased by too much and allows more transactions to be submitted than it should.
Was this helpful?