Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦
📦 npm
Not in CISA KEV
LOW severity

GHSA-xh2p-7p87-fhgh

LOWFix: liquity/dev@c69d0ba

GHSA-xh2p-7p87-fhgh is a low-severity (CVSS 3.1) vulnerability in @liquity/contracts. O3 Security confirms whether GHSA-xh2p-7p87-fhgh is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

Incorrect TCR calculation in batchLiquidateTroves() during Recovery Mode

Published
Aug 5, 2021
Updated
Jul 29, 2021
Affected
1 pkg
Patched
None yet
Exploits
None indexed
Exploitation data as of Jul 29, 2021 · OSV.dev, FIRST.org (EPSS)

Real-World Exposure

1 pkg affected
📦@liquity/contracts

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects npm packages — download data is not available via public APIs for these ecosystems.

Description

TCR is temporarily miscalculated in the batchLiquidateTroves function during Recovery Mode.

The bug lies in batchLiquidateTroves of TroveManager.

When calculating system's entire collateral, we should also exclude the liquidated trove's surplus collateral, since liquidation closes the trove and makes the surplus collateral claimable by the trove owner. This means, this line of code should look like this:

vars.entireSystemColl = vars.entireSystemColl.sub(singleLiquidation.collToSendToSP).sub(singleLiquidation.collSurplus);

Impact

The miscalculated entire collateral is used only to calculate the TCR and check if the system has been able to exit Recovery Mode. The miscalulation only persists temporarily, and within thebatchLiquidateTroves transaction. Once the transaction completes the TCR and Recovery Mode will be calculated properly again. However, the bug could negatively impact the liquidation throughput and the gas efficiency gains from batching multiple liquidations in a single transaction.

In normal situations, the impact of the collateral surplus of a Trove on the global TCR would be tiny. For instance, we have calculated that liquidating a trove with a collateral representing 1% of the total system collateral (so in the order of at least $10M at current values), would lead to an extra 0.53% in the temporary miscalculation of TCR. So for this bug to be meaningful, in such a scenario, the resulting real TCR must be already be very close to the Recovery Mode boundary anyway - i.e. between 149.47% and 150%. The batch liquidation transaction should also be executed with a particular trove ordering to achieve the TCR distortion. When a different trove order for the liquidation transaction is selected, the bug has no impact. In summary, the bug only has a non-negligible impact in a very narrow, specific set of circumstances.

The potential effects of the bug after it occurs are:

  • The next trove in the sequence is not liquidated because the batchLiquidateTroves function calculates a premature exit from Recovery Mode. It could be liquidated in a subsequent transaction if the price of Ether doesn’t recover.
  • The next trove in the sequence has an ICR below 100% and it’s offset against the Stability Pool instead of redistributed among other troves because the function calculates a premature exit from Recovery Mode. For this to happen, the Ether price must have instantly plummeted by more than 10% (otherwise, the trove would have been already liquidated before).
  • The next trove in the sequence is liquidated while its ICR is over the real TCR: the function calculates the TCR as being slightly too high, and thus can liquidate a trove that has ICR less than the calculated TCR, but greater than the true TCR. This is probably the worst outcome - however it is already possible to achieve the same effect, regardless of the bug. A liquidator can craft a batchLiquidateTroves transaction whereby they select troves for liquidation such that the TCR increases and makes a given trove liquidateable. To liquidate trove A, they can order troves such that they first liquidate troves which raise the TCR to between A's ICR and 150%. This is intended and expected behavior. As clearly stated in Liquity documentation, to be completely safe and guarantee immunity from liquidation in Recovery Mode, borrowers should keep their ICR above 150%.

We don't believe this bug creates a profitable exploit. Theoretically, and only in a very narrow set of circumstances, a liquidator could try to send a batch liquidation during Recovery Mode that lets the system very temporarily return to Normal Mode earlier than it should. In that case - and only if the Ether price also happens to suddenly plummet by more than 10% - stability providers might take the haircut that should be taken by the borrowers (through redistribution).

Patches

The problem has been patched in the source code but not on mainnet contracts. Liquity protocol is immutable, and this issue is not critical, so it doesn't merit a launch of a new version.

Bug bounty

A reward of $1,000 (the maximum for its category) was awarded to Xiahong (gaoxh06) for reporting this bug.

For more information

If you have any questions or comments about this advisory:

Affected Packages

1 total
EcosystemPackageVulnerable rangeFix
📦npm@liquity/contractsall versionsNo fix

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for @liquity/contracts. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Remediation status

    No patched version of @liquity/contracts has shipped for GHSA-xh2p-7p87-fhgh yet. Where your build allows, override or pin the dependency away from the vulnerable range, and apply any maintainer-recommended mitigation.

  3. Mitigate without a patch

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-xh2p-7p87-fhgh is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-xh2p-7p87-fhgh. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

TCR is temporarily miscalculated in the `batchLiquidateTroves` function during Recovery Mode. The bug lies in [`batchLiquidateTroves` of `TroveManager`](https://github.com/liquity/dev/blob/7c83ea11378454629618b3808b16fbfda69ee3e5/packages/contracts/contracts/TroveManager.sol#L722). When calculating system's entire collateral, we should also exclude the liquidated trove's surplus collateral, since liquidation closes the trove and makes the surplus collateral claimable by the trove owner. This means, this line of code should look like this: ``` vars.entireSystemColl = vars.entireSystemColl.s
O3 Security · Impact-Aware SCA

Is GHSA-xh2p-7p87-fhgh in your dependencies?

O3 detects GHSA-xh2p-7p87-fhgh across npm dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.

GHSA-xh2p-7p87-fhgh: @liquity/contracts… | O3 Security