<!-- canonical: https://0xsimao.com/findings/tenor-markets-overflows-sell-offer-reads -->

# ClampLib::mulDivUpInverse overflows on large `target`, DoSing SELL offer reads

Low/Info · Blackthorn · Fixed-rate lending · 22nd May, 2026

Finding L-5 of the Tenor Morpho Migrations security review.

- Protocol: https://www.tenor.finance/
- Report: /reports/tenor-markets
- Source: https://github.com/tenor-labs/tenor-contracts/blob/main/audits/2026-06-Blackthorn-review.pdf

---

## Summary

`mulDivUpInverse` computes `target * den` via `mulDivDown` without an overflow guard. Since `den == WAD` and `num == price` (typically `num <= den`), `target * WAD` overflows for `target > type(uint256).max / WAD`. The sibling `mulDivDownInverse` does guard this case and returns `type(uint256).max`.

## Vulnerability Detail

The [helper](https://github.com/sherlock-audit/2026-05-tenor-markets-may-21st-2026/blob/main/tenor-morpho-v2-contracts-2/src/periphery/libraries/ClampLib.sol#L244-L246):

```solidity
function mulDivUpInverse(uint256 target, uint256 den, uint256 num) internal pure returns (uint256) {
    return target.mulDivDown(den, num);
}
```

`getOfferRemaining` calls this with `target = remainingAssets` (derived from `offer.maxAssets`) and `den = WAD = 1e18` for SELL offers:

```solidity
uint256 units =
    offer.buy ? mulDivDownInverse(remainingAssets, WAD, price) : mulDivUpInverse(remainingAssets, WAD, price);
```

`offer.maxAssets` is a `uint256` chosen by the maker. Any value above `~1.16e59` (`type(uint256).max / 1e18`) overflows inside `mulDivDown`, reverting the entire `getOfferRemaining` call. `mulDivDownInverse` handles the symmetric situation gracefully by returning `type(uint256).max` and capping further via `UtilsLib.min(units, type(uint128).max)`, but `mulDivUpInverse` does not.

A SELL offer maker who sets `offer.maxAssets` above this threshold (or `type(uint256).max` as an "unbounded" sentinel) DoSes any path that reads remaining capacity for the offer.

## Impact

SELL offers with sufficiently large `maxAssets` revert on any clamp read, breaking the router's clamp path and any external integration that calls `getOfferRemaining` for the offer.

## Code Snippet

https://github.com/sherlock-audit/2026-05-tenor-markets-may-21st-2026/blob/main/tenor-morpho-v2-contracts-2/src/periphery/libraries/ClampLib.sol#L245

## Tool Used

Manual Review

## Recommendation

Mirror the guard from `mulDivDownInverse`:

```solidity
function mulDivUpInverse(uint256 target, uint256 den, uint256 num) internal pure returns (uint256) {
    if (target == 0) return 0;
    if (den > type(uint256).max / target) return type(uint256).max;
    return target.mulDivDown(den, num);
}
```

---

Related findings:

- [mul() and div() in NFTPMath are misleading due to scaling by 1e18 underneath](https://0xsimao.com/findings/nftperp-i-nftp-scaling-1e18-underneath): Nftperp Exchange
- [`LenderCommitmentGroup_Smart` does not use `mulDiv` when converting between token and share amounts, possibly leading to DoS or loss of funds](https://0xsimao.com/findings/teller-finance-mul-div-converting-share): Teller Finance
