How to get paid $800k for a Clippy warning

Retrospective on a critical bridge vulnerability that was detectable by a linter

The largest bug bounty reward paid by Aurora last year was $800k, which is a big step down from $6m the year before. This post is a public retrospective and my thoughts on how to prevent similar bugs in the future.

What was the bug about

Rainbow Bridge allows sending tokens across blockchain networks by locking assets in a custodian contract on one chain, and minting new tokens on another.

The bug was in a component that is responsible for minting on the NEAR side. It allowed an attacker to drain all tokens from the custodian contract – one of the most critical bugs that could have devastated the entire NEAR ecosystem.

The core of the issue was in the log_index sent with the bridging “proof”:

#[serializer(borsh)] log_index: u64;

That value was converted to usize by the recipient contract, which is normally not a problem as it matches u64:

assert_eq!(receipt.logs[log_index as usize], log_entry);

However, on NEAR, the Rust code is compiled to wasm32 and as result the log_index is truncated.  By re-using the "proof" with a modified log_index value an attacker could mint tokens 2**32 times and drain all the assets in the custodian contract.

As simple as that!

Why it happened

We were well aware about wasm32 but the as_conversions Clippy check was not enabled for the affected component in the repository – our linter setup failed us. Our defensive programming practices were not good enough. The problem also wasn’t caught by internal reviews nor by multiple big-name auditors. 

Security is a cake and it’s absolutely required to have many layers! Stay humble and expect that any single measure could fail.

I promise to talk more about cakes later, but today I would like to say a couple of words about Clippy. 

Rust linting tips

Here is the bare minimum you have to do for setting up Clippy for security-sensitive applications:

  • Explicitly enable all Clippy lints. Yes, you must enable all of them: for example, as_conversions are not included even in the pedantic group.
  • Keep track of every single Rust component in your monorepos and make sure that Clippy configuration is consistent everywhere and that linter checks are actually executed in the CI.
  • Make sure that Clippy config in the local development environments matches security CI workflows

While it may sound like a lot of work to address every single Clippy warning, it’s fast and simple compared to the rest of the security measures typically required – just don’t overlook the basics.

Hope these simple tips could prevent a hack of a decentralized bridge or two.

Kudos to Goohong Jung (cybermong) for responsibly disclosing the vulnerability and taking a well deserved place in our hall of fame.

Subscribe to Point of Indifference

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
[email protected]