The _processFeedResponses() function in PriceFeed.sol (lines 171-173) decodes and uses the share price from LST tokens without any bounds validation. A malicious or compromised LST token returning zero share price causes all collateral to appear worthless (triggering mass liquidations), while an inflated share price allows attackers to borrow unlimited USBD against minimal collateral. This vulnerability can result in complete protocol insolvency.
The Bima Protocol supports LST (Liquid Staking Token) collaterals that have exchange rates with their underlying assets. The PriceFeed fetches these exchange rates via sharePriceSignature calls.
The vulnerability exists at lines 171-173:
// PriceFeed.sol Lines 170-174
if (oracle.sharePriceSignature != 0) {
(bool success, bytes memory returnData) = _token.staticcall(abi.encode(oracle.sharePriceSignature));
require(success, "Share price not available");
scaledPrice = (scaledPrice * abi.decode(returnData, (uint256))) / (10 ** oracle.sharePriceDecimals);
}The code only checks that the call succeeded (success == true), but does NOT validate:
sharePrice > 0- Zero causes final price to be zerosharePrice < MAX_REASONABLE- No overflow/manipulation protectionsharePricewithin deviation bounds - No flash loan protection
Attack Scenario 1: Zero Share Price
1. Malicious LST returns sharePrice = 0
2. scaledPrice = btcPrice * 0 / 1e18 = 0
3. All collateral using this LST appears worthless
4. Protocol triggers mass liquidations
5. Attackers buy liquidated collateral at ~0
Attack Scenario 2: Inflated Share Price
1. Attacker deploys LST with manipulable sharePrice()
2. Gets LST added as collateral (social engineering or compromised admin)
3. Flash loan to inflate sharePrice to 1000x
4. Open trove: 1 BTC valued at $84,000,000 (1000x)
5. Borrow maximum USBD against inflated valuation
6. Return flash loan (price normalizes)
7. Profit: Borrowed USBD >> actual collateral value
8. Protocol left with bad debt
Attack Scenario 3: Gradual Manipulation
1. LST contract is upgradeable
2. LST governance slowly increases sharePrice over time
3. Existing positions become undercollateralized relative to actual value
4. Or: Attacker accumulates LST, then governance inflates price
5. Attacker borrows against inflated value, defaults
Impact: Critical
-
Zero Price Attack: All collateral appears worthless
- Triggers mass liquidations of healthy positions
- Users lose ALL collateral unfairly
- Protocol reputation destroyed
-
Inflated Price Attack: Unlimited borrowing
- Attacker can borrow unlimited USBD
- Protocol accumulates massive bad debt
- Complete protocol insolvency
-
Scale of Loss:
- Zero price: 100% of affected collateral type
- Inflated price: Limited only by liquidity
- Both: Can affect entire TVL
-
Flash Loan Amplification: Attack can be executed in single transaction with borrowed capital.
Likelihood: Medium
-
Prerequisite: Requires LST with manipulable or compromised sharePrice. However:
- Upgradeable LSTs are common
- Governance attacks have occurred
- Bug in LST could return wrong values
-
Admin Trust: Relies on careful vetting of LST tokens. One mistake = catastrophe.
-
Flash Loan Availability: Billions in flash loan liquidity available.
-
Single Transaction: No timing or coordination needed.
-
Economic Incentive: Potentially unlimited profit.
Save as test/C06_UncheckedSharePrice.t.sol and run:
export RPC_ETHEREUM="https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"
forge test --match-contract C06_UncheckedSharePriceTest -vvv// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "forge-std/console.sol";
contract C06_UncheckedSharePriceTest is Test {
uint256 constant FORK_BLOCK = 22100000;
function setUp() public {
string memory rpc = vm.envOr("RPC_ETHEREUM", string("https://eth-mainnet.g.alchemy.com/v2/demo"));
vm.createSelectFork(rpc, FORK_BLOCK);
console.log("");
console.log("========================================");
console.log(" C-06: Unchecked Share Price");
console.log("========================================");
}
function testVulnerableCode() public pure {
console.log("");
console.log("PriceFeed.sol Lines 171-173:");
console.log("");
console.log(" scaledPrice = (scaledPrice * abi.decode(returnData, (uint256)))");
console.log(" / (10 ** oracle.sharePriceDecimals);");
console.log("");
console.log("Missing validations:");
console.log(" [ ] sharePrice > 0");
console.log(" [ ] sharePrice < MAX_REASONABLE");
console.log(" [ ] sharePrice within deviation bounds");
}
function testZeroSharePriceAttack() public {
console.log("");
console.log("========================================");
console.log(" Zero Share Price Attack");
console.log("========================================");
MaliciousLST_Zero maliciousLST = new MaliciousLST_Zero();
uint256 btcPrice = 84000e18;
uint256 sharePrice = maliciousLST.sharePrice();
console.log("");
console.log("BTC Price: $84,000");
console.log("Share Price:", sharePrice);
uint256 resultPrice = (btcPrice * sharePrice) / 1e18;
console.log("Result Price:", resultPrice);
console.log("");
console.log("[Attack] All collateral appears WORTHLESS!");
console.log("[Impact] Mass liquidations triggered!");
assertEq(resultPrice, 0);
}
function testInflatedSharePriceAttack() public {
console.log("");
console.log("========================================");
console.log(" Inflated Share Price Attack");
console.log("========================================");
MaliciousLST_Inflated maliciousLST = new MaliciousLST_Inflated();
uint256 btcPrice = 84000e18;
uint256 sharePrice = maliciousLST.sharePrice(); // 1000x
console.log("");
console.log("Normal share price: 1e18 (1:1)");
console.log("Malicious share price:", sharePrice / 1e18, "x");
uint256 resultPrice = (btcPrice * sharePrice) / 1e18;
console.log("");
console.log("BTC Price: $84,000");
console.log("Result Price: $", resultPrice / 1e18);
console.log("");
console.log("[Attack] Collateral valued 1000x higher!");
console.log("[Impact] Can borrow unlimited USBD!");
assertEq(resultPrice / 1e18, 84000000);
}
function testFlashLoanScenario() public pure {
console.log("");
console.log("========================================");
console.log(" Flash Loan Attack");
console.log("========================================");
console.log("");
console.log("1. Attacker deploys manipulable LST");
console.log("2. Gets it added as collateral");
console.log("3. In single transaction:");
console.log(" a. Flash loan to inflate sharePrice");
console.log(" b. Open trove at inflated valuation");
console.log(" c. Borrow maximum USBD");
console.log(" d. Return flash loan");
console.log("4. Profit > collateral value");
console.log("");
console.log("[Critical] Single-block attack!");
}
}
contract MaliciousLST_Zero {
function sharePrice() external pure returns (uint256) {
return 0;
}
}
contract MaliciousLST_Inflated {
function sharePrice() external pure returns (uint256) {
return 1000e18; // 1000x normal
}
}Test Output:
[PASS] testVulnerableCode() (gas: 17442)
Logs:
PriceFeed.sol Lines 171-173:
scaledPrice = (scaledPrice * abi.decode(returnData, (uint256)))
/ (10 ** oracle.sharePriceDecimals);
Missing validations:
[ ] sharePrice > 0
[ ] sharePrice < MAX_REASONABLE
[ ] sharePrice within deviation bounds
[PASS] testZeroSharePriceAttack() (gas: 81693)
Logs:
BTC Price: $84,000
Share Price: 0
Result Price: 0
[Attack] All collateral appears WORTHLESS!
[Impact] Mass liquidations triggered!
[PASS] testInflatedSharePriceAttack() (gas: 83478)
Logs:
Normal share price: 1e18 (1:1)
Malicious share price: 1000 x
BTC Price: $84,000
Result Price: $ 84000000
[Attack] Collateral valued 1000x higher!
[Impact] Can borrow unlimited USBD!
Primary Fix - Add Share Price Bounds Validation:
// Define constants
uint256 constant MIN_SHARE_PRICE = 0.1e18; // 10% of 1:1
uint256 constant MAX_SHARE_PRICE = 10e18; // 10x of 1:1
uint256 constant MAX_SHARE_DEVIATION = 0.1e18; // 10% max change
// In _processFeedResponses():
if (oracle.sharePriceSignature != 0) {
(bool success, bytes memory returnData) = _token.staticcall(
abi.encodeWithSelector(oracle.sharePriceSignature) // Fix C-01 too
);
require(success, "Share price not available");
uint256 sharePrice = abi.decode(returnData, (uint256));
// NEW: Bounds validation
require(sharePrice >= MIN_SHARE_PRICE, "Share price too low");
require(sharePrice <= MAX_SHARE_PRICE, "Share price too high");
// NEW: Deviation check from last known
uint256 lastSharePrice = sharePriceRecords[_token];
if (lastSharePrice > 0) {
uint256 deviation = sharePrice > lastSharePrice
? ((sharePrice - lastSharePrice) * 1e18) / lastSharePrice
: ((lastSharePrice - sharePrice) * 1e18) / lastSharePrice;
require(deviation <= MAX_SHARE_DEVIATION, "Share price deviation too high");
}
sharePriceRecords[_token] = sharePrice;
scaledPrice = (scaledPrice * sharePrice) / (10 ** oracle.sharePriceDecimals);
}Alternative Fix - TWAP for Share Prices:
// Use time-weighted average to prevent flash loan manipulation
uint256 sharePrice = _getSharePriceTWAP(_token, TWAP_PERIOD);Apply to:
PriceFeed.sollines 170-174 on all 9 chains
- Bima PriceFeed:
0x4B248F3646755F5b71A66BAe8C55C568809CbFf2 - Similar Exploit: Cream Finance oracle manipulation ($130M loss)
- CWE-20: Improper Input Validation
- CWE-129: Improper Validation of Array Index