Summary
In Bor’s parallel EVM functionality incorrect transaction execution can happen, which might result into inconsistent state and longer time in confirmation of blocks. An executor’s transaction nth can access and modifies the data processed by another transaction, thus leading to contradictory output after processing the same block sequentially and parallely.
Root Cause
The function StateDB.createObject
will append the journal with a resetObjectChange
. The field prev
of resetObjectChange
is a pointer to a state object returned by StateDB.getDeletedStateObject
. This pointer might come from the StateDB of a different executor process, which is handled by StateDB.mvRecordWritten
in many setter methods of StateDB. However, in StateDB.createObject
, the prev
pointer is directly stored into journal instead of a deep copy. To access this pointer, we can trigger resetObjectChange.revert
to run, which stores the prev
pointer into the StateDB of current executor process. After that, two different executor processes will have access to the same state object in memory. An attack scenario that utilizes this bug to cause asset duplication will involve two transactions in one block:
- Transaction i: is guaranteed to be executed exactly once by parallel EVM and complete (writes are flushed to MVHashMap) before transaction j reads its StateDB.
- Transaction j: Transaction j is crafted so that it will be executed more than once
The flow of the exploit looks something like this:
- In transaction i, attacker transfers some money to a receiver address (attacker can deploy to receiver address later). This will implicitly create a state object for receiver in StateDB of transaction i.
- In first execution of transaction j, attacker deploys contract to receiver address, which will triggers **
StateDB.createObject
**to store aresetObjectChange
withprev
pointing to receiver state object of transaction i. The deployment will intentionally revert to invokeresetObjectChange.revert
to give transaction j access to the receiver state object of transaction i. Then, attacker transfers an amount n to receiver. If the transfer happens before transaction i settles (writes to final StateDB), receiver’s balance will increase by n. - In second execution of transaction j, attacker transfers an amount n to receiver again. However, the receiver’s balance was increased by n in first execution of transaction j. Therefore, receiver’s balance will increase by 2n at the end of second execution. This is an asset duplication.
Resolution and Recovery
A patch was successfully released on 12th August, with Bor tag **v1.3.7** and commit.
It consists of one https://github.com/maticnetwork/bor/commit/bd5ed4c2c3204770b978b84eaa884fbe02acb532, where in function StateDB.createObject
, the deep copy of the state is passed to journal by changing how prev
parameter is handled. The implemented behaviour has been covered with unit tests.
The patch was first tested on a devnet, then tested and rolled out on Amoy and Mainnet nodes simultaneously . A release announcement was shared, allowing all the validators to upgrade.