# repayBorrow is inaccessible by overdue borrowers

UToken.repayBorrow reverts for overdue borrowers because it calls UserManager.updateFrozenInfo, which is onlyComptroller, permanently freezing lender funds.

- Payout: Contest reward
- Severity: high
- Program: Union Finance
- Credited: hyh
- Disclosed: 2022-11-04
- Write-up quality: 7/10
- Original: https://github.com/sherlock-audit/2022-10-union-finance-judging/issues/133
- Library page: https://bugbountylibrary.com/r/sherlock%3A2022-10-union-finance%3AH-3
- Tags: domain:web3 impact:dos impact:funds-loss kind:contest platform:sherlock sev:high tech:solidity technique:source-review vuln:access-control vuln:business-logic vuln:dos

**Target:** UToken/UserManager lending contracts
**Root cause:** _repayBorrowFresh calls IUserManager.updateFrozenInfo when borrower is overdue, but updateFrozenInfo is gated by onlyComptroller, so the call always reverts for overdue borrowers.
**Steps:**
1. Borrower takes loan via UToken and becomes overdue (lastRepay stale).
2. Borrower calls `repayBorrow(amount)` with amount >= interest.
3. `_repayBorrowFresh` computes isOverdue = checkIsOverdue(borrower) = true.
4. Execution reaches `IUserManager(userManager).updateFrozenInfo(borrower, 0)`.
5. UserManager's onlyComptroller modifier reverts with AuthFailed since msg.sender is UToken, not comptroller.
6. Repayment fails; lastRepay never updated; overdue status persists permanently.
**How found:** Code reading of _repayBorrowFresh's overdue branch and cross-checking the access modifier on UserManager.updateFrozenInfo (onlyComptroller) versus the caller (UToken).
**Exploitation:** No attacker action needed; any overdue borrower's repayment path is bricked, permanently freezing lender principal since repayBorrow is the only way to clear overdue status.
**Insight:** When auditing cross-contract calls, verify the callee's access modifier matches the actual caller contract, not just the intended role; a role-gated function invoked by a sibling contract bricks the whole flow.
