# Zero-balance collateral opt-in can make unhealthy positions unliquidatable

K2's set_user_use_reserve_as_coll lets borrowers mark zero-balance reserves as collateral, bloating account-data loops until liquidation calls exceed Soroban budget limits and unhealthy positions become unliquidatable.

- Payout: Contest reward
- Severity: high
- Program: K2
- Credited: ChainSentry
- Disclosed: 2026-08-20
- Write-up quality: 7/10
- Original: https://code4rena.com/reports/2026-04-k2#h-12
- Library page: https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AH-12
- Tags: domain:web3 impact:dos impact:funds-loss kind:contest platform:code4rena sev:high tech:solidity technique:incomplete-fix technique:source-review vuln:access-control vuln:business-logic vuln:dos

**Target:** Soroban smart contract router (kinetic-router) collateral/liquidation entrypoints
**Root cause:** set_user_use_reserve_as_coll(true) writes a collateral bitmap bit without checking the caller holds a nonzero aToken balance or enforcing MAX_USER_RESERVES=15. Unified account-data then iterates and prices every marked reserve, so phantom bits amplify runtime cost.
**Steps:**
1. Open a normal 2-asset position (1 collateral, 1 debt) with HF < 1.0.
2. Call `set_user_use_reserve_as_coll(user, asset, true)` on 24+ extra active reserves where the user's aToken balance is zero.
3. Confirm total_collateral_base and total_debt_base are unchanged versus control.
4. Call `liquidation_call` on the borrower; unified account-data iterates all 26 active slots, batch-fetches prices and calls `balance_of_with_index` per slot.
5. Observe `HostError: Error(Budget, ExceededLimit)` panic before any repayment; control borrower liquidates fine.
6. Same root cause via `get_user_account_data` panics at 34 active slots (32 extra toggles).
**How found:** Read protocol docs stating MAX_USER_RESERVES=15 bounds health-factor loops and prevents fragmentation; noticed set_user_use_reserve_as_coll only checks reserve existence/active/oracle price, not user balance or the cap, then traced unified account-data iterating every bitmap bit and being invoked by liquidation before repayment.
**Exploitation:** Borrower self-marks many unfunded reserves as collateral via the public entrypoint, leaving real collateral/debt untouched. Any liquidator's liquidation_call spends Soroban budget pricing and reading phantom positions, panicking before debt repayment, so the unhealthy position cannot be liquidated.
**Escalation:** Thresholds: direct liquidation fails at 26 active slots (24 extra toggles), account-data at 34 slots (32 toggles) — well below the 64-bit bitmap max, defeating the documented 15-reserve anti-fragmentation cap.
**Insight:** When a protocol documents a reserve/loop cap as an anti-DoS guarantee, audit every public entrypoint that writes the bitmap for missing cap and ownership checks — a zero-value state write can still amplify downstream loop cost into a budget-exhaustion DoS on liquidation.
