Returndata Bombing RAI's Liquidation Engine - A Critical Bug Worth $0
- Or C.
- 36 minutes ago
- 7 min read
How a spec-compliant savior contract crashes liquidations, and why governance rubber stamp became the perfect excuse to avoid paying bounties.
Today we're disclosing a vulnerability in RAI (Reflexer Finance) that could have rendered positions unliquidatable, leading to protocol insolvency. The bug was acknowledged as technically valid by all parties. Immunefi mediation initially ruled Medium severity and recommended payout. Then, with no new technical information, they reversed to "None" severity. No fix, no pay.
This bug was reported in September 2023. It's now January 2026, and the vulnerability remains unpatched in the geb repository. To understand why it's safe to disclose, why the issue is silently patched at the gov level, and how projects can easily avoid paying bounties, read on.
Intro
RAI is an ETH-backed stable asset with a managed float regime. Unlike DAI which pegs to $1 through governance-controlled mechanisms and accepts dozens of collateral types, RAI uses only ETH collateral and an algorithmic controller to manage its floating redemption price - designed from day one to progressively minimize governance intervention.
Like any lending protocol, positions that fall below the required collateralization ratio must be liquidatable. This is the foundational building block that keeps the system solvent.
RAI introduced an interesting feature called Safe Saviours - contracts that can attempt to rescue an underwater position during liquidation by injecting additional collateral. The docs openly encourage the community to build savior contracts, stating that anyone can do so as long as they fulfill the specified requirements.
What could go wrong?
The Liquidation Engine
Let's look at how the LiquidationEngine handles the savior mechanism in liquidateSAFE():

The design intent is clear: call the savior, let it attempt a rescue, but if anything goes wrong, catch the error and proceed with liquidation anyway. The try/catch pattern should make this bulletproof, right?
Two details caught our attention:
The call to saveSAFE() does not limit gas spending, defaulting to the 63/64 rule
The catch clause receives an arbitrary-length bytes type
If you've done enough smart contract security work, alarm bells should be ringing.
The bug
Enter the returndata bomb attack.
When a contract reverts, it can specify arbitrary return data. The EVM's RETURNDATACOPY opcode is used by the caller to copy this data into memory, and memory expansion costs gas. A malicious contract can exploit this by reverting with massive return data, forcing the caller to spend enormous gas just to receive the error message.
Here's how it applies to RAI:
The attacker deploys a savior that calls revert(0, HUGE_NUMBER)
The saveSAFE() call uses 63/64 of available gas to construct the massive returndata
The LiquidationEngine has only 1/64 of gas remaining
The catch (bytes memory revertReason) clause triggers RETURNDATACOPY to copy the massive returndata
But copying that data requires ~63/64 of the original gas, which we don't have
Out of gas. The entire liquidateSAFE() call reverts.
The result: an unliquidatable position. The safe owner can watch their collateral ratio plummet to zero while laughing all the way to insolvency.
Proof of concept
The following code was provided to demonstrate the core behavior of the returndata bomb.

The amount of gas is entered into callLiqWithGas(), which calls liquidateSAFEPOC() with an exact gas count to ease the demonstration. The function calls saveSAFE(), which has simplified logic sufficient to demonstrate that it can crash a liquidation regardless of the gas amount.
It's essentially a switch statement that sizes the returndata to suit the passed gas count. It will work for any gas amount up to 80k, after which additional if branches would be needed. A proper malicious implementation would solve the memory-expansion gas quadratic equation dynamically so that hard-coded values aren't needed.
Impact
When a position cannot be liquidated, bad debt accumulates as collateral value drops below the debt owed. Even if governance eventually deregisters the savior and forces liquidation, the damage is done - the protocol must auction off insufficient collateral to cover the RAI debt. The stability pool absorbs the shortfall. Bad debt in lending protocols is permanent. It's how protocol insolvency happens.
The governance rubber stamp
RAI's response was essentially:
Saviors must be whitelisted by governance. Therefore, this requires privileged access. Out of scope.
Here's why this argument doesn't hold.
The savior onboarding process, per RAI's own documentation, requires:
Two audits
Community developer feedback
Testnet deployment period
Here's the critical question: Would any of these checks catch this bug?
The answer is no.
The returndata bomb isn't a property of the savior contract in isolation - it's an emergent behavior from the interaction between a reverting savior and the LiquidationEngine's unbounded catch clause. An auditor reviewing a savior that queries an oracle (which might revert with large data) would see nothing suspicious. The savior follows all documented requirements. It doesn't "look" malicious.
The bug is in the LiquidationEngine, not the savior.
RAI's counterargument:
Governance can screw up the system in many ways, introducing a malicious savior is just one of them.
They compared it to governance, adding a malicious oracle. But there's a crucial difference: governance would not be wrong to approve this savior. It fulfills all requirements. The LiquidationEngine has a vulnerability that governance's review process cannot detect.
This isn't malicious governance or incompetent governance. It's a technical smart contract bug that bypasses all checks made before whitelisting.
Severity downgrade
Immunefi's first mediation ruled Medium and recommended payout. Their reasoning for downgrading from Critical:
This bug does not lead to protocol insolvency, and the owner can disable a savior if needed.
We disagreed. When a SAFE cannot be liquidated, collateral doesn't cover the RAI debt in the system. Bad debt accumulates during the window before governance deregisters the savior. The attacker controls when to re-enable liquidations - they simply wait until maximum damage is done.
The debt auction mechanism would need to raise capital with insufficient collateral backing. The stability pool loses funds covering the shortfall.
Immunefi categorized this as Griefing - Medium. But griefing implies recoverable damage. Bad debt is not recoverable - it's a permanent loss to the protocol. The attacker also has clear incentive: avoiding liquidation of their underwater position while retaining control over when (or if) to disable the savior. The ability to avoid liquidation is an enabler for various exploitation strategies, for example - opening two opposite leveraged positions. If the positions are not liquidateable, a change is any direction is profitable, since the downside is capped to the posted collateral, while the upside is uncapped.
Mediation timeline
The disclosure timeline:
Date | Event |
Sept 19, 2023 | Report submitted as Critical (Protocol Insolvency) |
Sept 20, 2023 | RAI closes immediately - "requires governance approval" |
Sept 20, 2023 | Mediation requested |
Sept 28, 2023 | Immunefi rules: Medium severity, recommends payout |
Sept 28, 2023 | RAI reopens, argues governance = trusted = out of scope |
Sept 28, 2023 | RAI closes again |
Oct 12, 2023 | Second Immunefi mediation: Impact changed to None |
Oct 18, 2023 | Final ruling: Out of scope, "depends on privileged address" |
Immunefi ruled Medium and recommended payout on September 28th. Then, with no new technical information, they reversed to "None" two weeks later.
The only thing that changed was RAI's insistence that governance involvement equals out-of-scope. Immunefi accepted this framing.
Per RAI's bounty program, Critical severity paid up to $100,000 (minimum $50,000), and Medium paid $10,000.

We received $0.
The Precedent problem
The ruling that anything requiring governance interaction is out of scope is dangerous:
1. It doesn't fulfill the purpose of bug bounties.
Bug bounties exist to find bugs before blackhats exploit them. A blackhat wouldn't be stopped by governance - they'd social engineer a spec-compliant savior through the approval process. The governance protection is an illusion, no more than a "get out of jail free" card for the project.
2. The project acknowledged it's a valid bug.
RAI never disputed the technical validity. Their only contention was scope. Yet they gained the knowledge to protect against this attack vector without paying for it.
3. It encourages projects to hide behind governance.
Any project can now argue: "This requires [admin/governance/multisig] to [approve/configure/enable] something, therefore it's privileged access, therefore out of scope." Even when the privileged action is exactly what the system is designed to do.
Bug theory
How do you find bugs like this?
1. Untrusted external calls are untrusted in all their properties.
Most developers think about reentrancy. Fewer think about return values. Even fewer think about returndata size. When you see an external call, ask: what assumptions is this code making about the callee's behavior? The LiquidationEngine assumed the savior's revert data would be reasonably sized. That assumption was never validated.
2. Try/catch is not a security boundary.
Solidity's try/catch gives a false sense of safety. But the catch mechanism itself can be attacked. The gas cost of receiving error data, the potential for the catch block to revert, state changes before the revert - all attack surface.
Developer best practices:
Use ExcessivelySafeCall for untrusted external calls
Cap gas on calls to external contracts
If catching errors, use catch { } instead of catch (bytes memory reason) when you don't need the error data. If you do, copy a capped amount using the RETURNDATACOPY opcode directly
Treat error messages as untrusted input
Key Takeaways
Returndata bombing can DoS contracts using try/catch with unbounded error data
Governance rubber stamp is not a security guarantee. If the approval process can't detect the bug, governance is not a mitigation
Bug bounty programs can weaponize "governance" arguments to avoid paying for legitimate vulnerabilities
Immunefi mediation is not binding and can reverse previous rulings with no new information
Final thoughts
We disclosed this vulnerability in good faith, through proper channels, with a POC demonstrating the core attack behavior. The bug was acknowledged as technically valid by all parties. Mediation marked the finding as eligible for a Medium bounty, despite downgrading severity from Critical (incorrectly, in my view).
A blackhat, instead of reporting it, would have deployed the malicious savior, shepherded it through governance review, and caused significant damage to the protocol. Instead, RAI got a free security consultation.
Hope you enjoyed dissecting this bug. We will continue to share tricks from the "how to not pay researchers for valid findings" BINGO card. Stay tuned for more.
About TrustSec
TrustSec is a world-renowned boutique auditing and bug bounty firm, founded by famed white-hat hacker Trust in 2022. Since then, we have published over 100 reports for a wide range of clients and received bug bounties from more than 40 projects. Composed exclusively of top competitive bounty hunters, TrustSec employs unique, fully collaborative methodologies to secure even the most complex codebases. Visit our website to learn why top names like Optimism, zkSync, OlympusDAO, The Graph, BadgerDAO, Reserve Protocol, and many others trust us as their security partner.





Comments