Bounty Program Helps Fix Contract Vulnerability
Photo by Zdeněk Macháček / Unsplash
Bug bounty security

Bounty Program Helps Fix Contract Vulnerability

Łukasz Zimnoch
Łukasz Zimnoch

As a bridge between Bitcoin and DeFi, the security of tBTC is vital. tBTC faced a critical vulnerability, but thanks to a security researcher and Threshold's Immunefi Bug Bounty Program, it was promptly addressed and all funds are safe. This program helps to improve the protocol's robustness and safety by encouraging security experts to identify and report issues. This recent resolution underscores the significance of such proactive measures in safeguarding tBTC and the broader DeFi ecosystem through responsible disclosure.

Summary

On Monday, October 9th 2023 at 12:30 AM UTC, we received an ImmuneFi report claiming that the L2WormholeGateway contract contained a critical vulnerability. Specifically, the report pointed out that a small amount of the Ethereum Mainnet (L1) tBTC token could be used to mint an indefinite number of Layer 2 (L2) tBTC tokens on four L2 chains where the tBTC v2 system was integrated: Arbitrum, Base, Optimism, and Polygon. After a thorough investigation, we confirmed that the report was valid at 09:45 AM UTC same day. Subsequently, we developed a patch, and used it to upgrade the vulnerable contract instances at 08:00 PM UTC. This was a preventative fix, funds are safe, and no assets were affected. Here, we describe the potential exploit scenario and specific actions we took to prevent it.

Understanding the contract

The L2WormholeGateway contract defines a receiveTbtc function that finalizes the tBTC token L1 → L2 bridging process using a Verified Action Approval (VAA) created and signed by Wormhole's off-chain Guardians. The VAA holds important information about the cross-chain transfer of assets and allows the Wormhole Bridge to perform the correct settlements on both chains.

Specifically, the receiveTbtc function:

  1. Uses the given VAA (encodedVm) to complete the transfer on Wormhole Bridge’s end
  2. Receives Wormhole wrapped tBTC as result (bridgeToken)
  3. Uses the received wrapped tBTC to mint the canonical tBTC token for the given L2 chain
function receiveTbtc(bytes calldata encodedVm) external {
    // (...)

    uint256 balanceBefore = bridgeToken.balanceOf(address(this));
    bytes memory encoded = bridge.completeTransferWithPayload(encodedVm);
    uint256 balanceAfter = bridgeToken.balanceOf(address(this));

    uint256 amount = balanceAfter - balanceBefore;
    require(amount > 0, "No tBTC transferred");

    // (...) Mint `amount` to the receiver pointed in the VAA
}

Potential attack vector

By design, the L2WormholeGateway does not delve into the implementation details of the Wormhole Bridge. The receiveTbtc function simply passes the received VAA to the Wormhole Bridge and expects to receive some Wormhole-wrapped tBTC in return (if the Wormhole Bridge deems the given VAA is valid). VAAs are purely constructs of Wormhole, so the Wormhole Bridge is the only place that should initiate transfers of any assets involved based on them. The combination of these asset transfers and the fact that receiveTbtc does not independently verify received VAAs is where the problem started.

A malicious actor could have :

  1. Created a custom ERC-20 token contract, e.g. MaliciousERC20
  2. Deployed it on L2 and created its wrapped Wormhole representation on L1 Ethereum chain
  3. Bridged some MaliciousERC20 tokens from L2 to L1
  4. Bridged the now wrapped tokens back from L1 to L2 and get some valid VAAs that could have been used to call receiveTbtc and bridge.completeTransferWithPayload in result

Of course, action (4) was not able to directly force the Wormhole Bridge to fill L2WormholeGateway with the Worhmole-wrapped tBTC token (bridgeToken) necessary to mint canonical L2 tBTC. This is because the VAA refers to L1-wrapped MaliciousERC20 instead of L1 tBTC, and the Wormhole Bridge can detect that. However, the bridge.completeTransferWithPayload call must invoke the MaliciousERC20.transfer function to unlock the malicious L2 tokens locked during (3). Theoretically, the malicious actor could have implemented MaliciousERC20.transfer in a way that affected the bridgeToken balance and forced the L2WormholeGateway to mint canonical L2 tBTC. Unfortunately, it turned out that this was actually possible in practice as the L2WormholeGateway contract additionally exposed the depositWormholeTbtc function:

function depositWormholeTbtc(uint256 amount) external {
    require(
   	 mintedAmount + amount <= mintingLimit,
   	 "Minting limit exceeded"
    );

    emit WormholeTbtcDeposited(msg.sender, amount);
    mintedAmount += amount;
    bridgeToken.safeTransferFrom(msg.sender, address(this), amount);
    tbtc.mint(msg.sender, amount);
}

The purpose of this function was to address an unlikely corner case where a depositor cannot automatically mint their canonical L2 tBTC and is left with the Wormhole-wrapped tBTC asset instead. This function was supposed to improve the user experience in that case. Otherwise, the depositor would have to abandon the L2 bridging process and claim back their L1 tBTC.

A malicious actor could have used MaliciousERC20.transfer to invoke depositWormholeTbtc which takes the depositor’s Wormhole wrapped tBTC, moves it to L2WormholeGateway and mints canonical L2 tBTC. This obviously causes a change of the L2WormholeGateway's wrapped tBTC balance (bridgeToken) once bridge.completeTransferWithPayload returns. In that case L2WormholeGateway could have minted the same amount again, doubling the amount of minted canonical L2 tBTC tokens. This pattern could have been repeated to mint a significant amount of non-backed canonical L2 tBTC and depeg tBTC from Bitcoin.

Mitigation

Fortunately, to mitigate the scenario described above, removing the depositWormholeTbtc function from the L2WormholeGateway contract was sufficient, which we promptly did. As a result, receiveTbtc is now the only point that can mint canonical L2 tBTC based on the change in Wormhole wrapped tBTC balance. To further enhance the security and resilience of this mechanism against unforeseen side effects triggered by the Wormhole Bridge contract, we also decided to make receiveTbtc non-reentrant. All of this work was carried out through a private GitHub security advisory. Once we completed all necessary code changes, we swiftly deployed new implementations of L2WormholeGateway on all four affected L2 chains and requested the Threshold Council to prepare the appropriate upgrade transactions pointing the transparent proxies to the new implementations.

Conclusion

In the ever-evolving landscape of decentralized finance, the security and integrity of our systems remain our utmost priority. One of the cornerstones of that priority is our collaboration with the security community via our ImmuneFi Bug Bounty Program. We sincerely appreciate the vigilance and expertise of the security community in collaborating with us to strengthen our platform. Security is an ongoing journey, not a destination, and together we can ensure a safer, more resilient future for Threshold.