This is the story of a simple bug in the FRAME runtime of Parity’s Substrate blockchain framework. The bug allowed attackers to do infinitely large transactions without paying any extra fees. The vulnerability was the result of a buggy implementation of fee calculation in the FRAME runtime of Substrate. By exploiting this vulnerability, an attacker could’ve added their manga collection to the payload of a simple transaction without paying any extra fees. This would’ve resulted in the manga collection becoming a part of the blockchain, forever. Cheapest decentralized blockchain storage!
Sample fees for a 2 megabyte payload before the bug-fix:
Sample fees for a 2 megabyte payload after the bug-fix:
Please note that this bug is now fixed and a runtime upgrade has been deployed to Kusama, Polkadot and Polymesh Aldebaran testnet.
The transaction fee is supposed to increase as the size of the transaction increases. There’s a constant
TransactionByteFee that is supposed to be added to the total fee for every byte of data contained in the transaction. This would’ve prevented the above exploit from working but due to a very tiny bug in the code, this fee was not being applied under certain circumstances.
The fee was being calculated as:
fee = base_fee + targeted_fee_adjustment * (len_fee + weight_fee);
base_feeis a static fee applied to all transactions (analogous to the 21k gas fee in Ethereum).
targeted_fee_adjustmentis a fee multiplier that is based on recent activity on the chain. If the recent blocks have been full, this multiplier will increase and if the recent blocks have been empty, this multiplier will decrease.
len_feeis the extra fee charged for the size of a transaction. Transactions with bigger payloads will have a bigger
weight_feeis based on the compute requirements of a transaction. A more resource-intensive transaction will have a higher
There are two problems here. Firstly, the
len_fee is being affected by the
targeted_fee_adjustment multiplier. However, as per the W3F docs, it should not be. The second problem is that the
targeted_fee_adjustment multiplier was allowed to go as low as zero. The combined result of these two problems is that when the chain is not busy, no
weight_fee are charged and anyone can submit large transactions to inflate the blockchain size.
The vulnerability was very easy to exploit. All you had to do was to send a huge payload alongside a valid transaction. Sample reproduction steps are as follows:
- Open the Polkadot UI – https://polkadot.js.org/apps.
- Click on the “Extrinsics” tab on the left.
- Select the “remark” extrinsic from the “system” frame.
- Click on “file upload” on the right and upload your payload (anything you want to put on the blockchain).
- Submit the transaction.
The fix was twofold but simple. Firstly, the
len_fee was moved out so that it was not affected by the
targeted_fee_adjustment multiplier. The new fee formula is
fee = base_fee + len_fee + targeted_fee_adjustment * weight_fee;
In addition, the
targeted_fee_adjustment was adjusted so that the minimum multiplier is one instead of zero. This brings the implementation closer to what the W3F research suggested. It makes exploit like the one mentioned in this post impossible.
- Reported – 17/06/2020
- Acknowledged – 17/06/2020
- Fixed – 17/06/2020
I’d like to thank Parity Technologies for being responsive and fixing the bug within hours of the report. Kusama and Polkadot were upgraded soon after the bug fix. Parity offered me a 250 USD bug bounty for this disclosure which they have kindly donated to the Smile Foundation on my behalf. I’d like to thank them for that as well.
Everyone is welcome to try their hands on Kusama and report any bugs to Parity. Below is a direct quote from Fredrik, CTO at parity:
We are thankful to Mudit for reporting this to us. We created Kusama for this specific purpose of testing the underlying economic assumptions before they reach Polkadot and are happy that it worked in this case. We encourage everyone to continue hunting bugs on Kusama and reporting them to us!Fredrik Harrysson, CTO at Parity