cantina-size Credit
cantina-size Credit
使用owner()函数,该合约已经设置了owner为零
漏洞代码
function reinitialize() external onlyOwner reinitializer(1_7_0) { |
后续在创造Market市场,使用owner()函数,直接使用的该合约的地址,为零
function createMarket( |
后面会对零地址的一个讨论,所以该创建市场始终不会成功
function validateOwner(address owner) internal pure { |
具体修改就是不使用owner(),使用管理者
+ address admin = msg.sender; |
gas消耗,调用函数,重复使用修饰
在 compensate() 函数中,使用了两次 shouldNotEndUpUnderwater
修饰符:
一次是在 compensateOnBehalfOf()
函数中应用,传入了externalParams.onBehalfOf
作为参数。
另一次是在 compensate()
函数内部,直接应用于 msg.sender
。
关键点是,``msg.sender 在
compensate()函数中等同于
externalParams.onBehalfOf,因为
compensate() 调用了
compensateOnBehalfOf(externalParams.onBehalfOf)`。
由于两者(msg.sender 和 externalParams.onBehalfOf)
是相同的,实际上第二次使用 shouldNotEndUpUnderwater(msg.sender)
是多余的。这样会导致在计算是否用户资金低于清算线(underwater)
时,执行了重复的检查,从而增加了不必要的 gas 消耗
通过前置交易还清债务,导致清算失败
借款人借钱并借款位置被清算:
假设借款人借了钱,市场发生波动,导致他们的抵押物(例如 WETH 或 USDC)价值下降,贷款比例超过了安全阈值(例如市场的风险配置)。
这时,清算者会尝试清算借款人的债务,回收他们的抵押物。
借款人如何规避清算:
借款人调用 compensate()
函数。这时,借款人偿还了他们的所有债务,并创建了一个新的债务位置。新位置可能是一个具有不同期限或条件的贷款。
这时,借款人“买时间”,原本的贷款债务被视为已经偿还,因为系统认为它已经“被清偿”了。
清算失败:
清算者尝试清算借款人原本的债务,但发现这个债务已经被“偿还”了,因为在 compensate() 中,借款人将原本的债务状态更新为“已偿还”,并创建了一个新的债务位置。
因此,清算者无法清算这个债务位置,导致清算操作失败。
前置交易的关键点:
前置交易(front-running)
:借款人通过调用compensate()
来提前偿还债务并创建新位置。这相当于“转移”了债务的负担,清算者就无法对原债务进行清算。
拖延清算时间:借款人通过反复创建新的债务位置,能够持续拖延清算,直到贷款的期限结束。
示例
假设借款人A(例如 Bob)借了 100 WETH,贷款期限为 7 天。如果市场不稳定,贷款在第3天就会达到清算阈值。清算者打算清算 Bob 的债务,但在清算前,Bob 调用了 compensate() 函数,偿还了他当前的债务,并创建了一个新的债务位置,这个新的债务位置可能会有不同的条件(例如不同的期限)。
当清算者再次尝试清算时,系统认为 Bob 的债务已经偿还(因为 compensate() 更新了债务状态),所以清算操作失败。Bob 通过这种方式“买了时间”,避免了清算。
影响
借款人:借款人通过这种方式避免了被清算,并且可以继续延缓清算,直到债务过期。
清算者:清算者无法收回债务,因为借款人通过 compensate()
创建了新的债务位置,导致原债务位置被视为已偿还。
升级合约时,没有显式的调用初始化函数
MulticallUpgradeable
和 AccessControlUpgradeable
合约都有各自的初始化函数(例如 __Multicall_init 和 __AccessControl_init
)。
这些初始化函数通常在合约部署时被调用,用于设置初始状态和配置。
然而,在合约升级(使用 reinitialize
)的过程中,除非显式调用初始化函数,否则这些合约的初始化函数不会自动被调用。
问题的关键
当升级 SizeFactory
合约时,reinitialize
函数会被调用,但它没有显式调用依赖合约(MulticallUpgradeable 和 AccessControlUpgradeable
)的初始化函数。
结果是,在升级之后,MulticallUpgradeable
和 AccessControlUpgradeable
合约不会被正确初始化。虽然你提到目前这个问题没有风险,因为它们的初始化函数不执行任何实际的逻辑,但如果将来这些合约的初始化函数发生变化或有逻辑执行,那么这可能会导致问题。
升级后的合约,一些函数缺少修饰,导致恶意用户调用
在升级后,createPriceFeed
和 createBorrowATokenV1_5
函数缺少授权修饰符,这意味着任何用户都可以调用这些函数。这是一个问题,因为它允许恶意用户将恶意的价格喂价或借贷代币加入白名单,而这些恶意合约可能会被 Size 市场使用。
例如,问题可能会引发以下情况:
恶意价格喂价:市场可能会使用一个恶意的 UniswapV3 池合约,该合约返回不准确的价格,或者返回偏袒某些地址的价格。恶意用户可以利用这一点,将实际的抵押率调整为高于真实值,从而防止可能的清算。
恶意借贷代币:恶意借贷代币可能会使用一个恶意的变量池,如果某些用户尝试调用 Size.withdraw
,则在 NonTransferrableScaledTokenV1_5.withdraw
中的 variablePool.withdraw
会因错误而被拒绝,从而阻止这些用户提款。
variablePool.withdraw(address(underlyingToken), amount, to)
;
过多的价格喂价/借贷代币:恶意用户可以创建大量的价格喂价或借贷代币,这将导致 getPriceFeeds
和 getBorrowATokensV1_5
调用失败,因为这两个函数会遍历所有实例,可能会超出区块的 gas 限制。此外,恶意用户还可以创建具有过长描述的价格喂价或借贷代币,导致调用 getBorrowATokenV1_5Descriptions
和 getPriceFeedDescriptions
时因为 gas 不足而回滚,或者需要大量 gas,最终导致资金损失。