EigenLayer的sidecar奖励计算中发现了一个除零错误,该错误可能导致所有AVS和运营商的拒绝服务。根本原因是duration参数验证不足,允许零值从智能合约传播到SQL查询。通过在链上和sidecar中添加显式检查,该问题在被利用之前得到了修复。
tldr: EigenLayer sidecar奖励计算中的除零错误可能导致所有AVS和operator的拒绝服务。根本原因是duration参数的验证不足,允许零值从智能合约传播到SQL查询。通过在链上和sidecar中添加显式检查,该问题在被利用之前得到了修复。
EigenLayer 将重新质押引入 Ethereum,允许质押的资产保护其他被称为积极验证服务 (AVS) 的应用程序。这个复杂的生态系统依赖于链上智能合约和链下组件 (sidecar),用于数据聚合和奖励计算等任务。这篇文章详细介绍了一个在 EigenLayer sidecar的奖励处理逻辑中发现的严重除零错误,解释了它的根本原因、潜在影响以及随后实施的修复。
EigenLayer sidecar充当支持核心EigenLayer智能合约的链下worker。它监听链上事件,处理数据,执行计算(如奖励计算),并将结果存储起来,通常在一个数据库中。
一个关键功能是计算保护AVS的operator和staker的奖励。AVS将奖励细节提交到链上的RewardsCoordinator.sol
合约。该合约发出sidecar监控的事件。然后,sidecar使用事件数据,包括奖励amount
和duration
,来计算和分配奖励,并存储结果以供以后分配。
RewardsCoordinator
和漏洞路径RewardsCoordinator.sol
合约是AVS奖励提交的入口。它包括诸如amount
和duration
(以秒为单位)之类的参数,表示获得奖励的时间。该合约对duration
执行了一个基本的验证:
// RewardsCoordinator.sol (补丁前)
require(duration % CALCULATION_INTERVAL_SECONDS == 0, InvalidDurationRemainder())
虽然这确保了duration是特定间隔的倍数,但此检查并不能阻止duration为零 (因为 0 % N == 0
)。因此,零duration可以在事件中发出。
该漏洞表现在sidecar的奖励计算逻辑中,由接受零duration奖励触发。
1. 摄取期间缺乏验证:
Sidecar的事件解析逻辑(例如,在operatorDirectedOperatorSetRewardSubmissions.go
中)直接从事件payload中读取duration
值,而不检查它是否为非零。
operatorDirectedOperatorSetRewardSubmissions.go#L160-L178
// 路径: /sidecar/pkg/eigenState/operatorDirectedOperatorSetRewardSubmissions/operatorDirectedOperatorSetRewardSubmissions.go
// 在 handleOperatorDirectedOperatorSetRewardSubmissionCreatedEvent 函数内:
// ... 其他字段被解析 ...
rewardSubmission := &OperatorDirectedOperatorSetRewardSubmission{
// ... 其他字段 ...
Duration: outputRewardData.Duration, // Duration直接读取,没有验证
// ... 其他字段 ...
}
此外,sidecar使用的PostgreSQL数据库schema将duration
列定义为bigint not null
。这阻止了NULL
但允许值为0
。因此,零duration奖励可以被成功解析和存储。
2. SQL计算中的除零:
后续的奖励处理步骤涉及SQL查询,该查询通过存储的duration
进行除法,以计算奖励率(例如,每天的token)。这种模式出现在负责不同奖励类型的多个Go文件中:
-- 在 _1_goldActiveRewardsQuery 内部
SELECT ..., amount / (duration / 86400) as tokens_per_day, ...
num_registered_snapshots
为0时发生) -- 在 _7_goldActiveODRewardsQuery 内部 (在CASE语句中)
WHEN ar.num_registered_snapshots = 0 THEN floor(ar.amount_decimal / (duration / 86400))
-- 在 _11_goldActiveODOperatorSetRewardsQuery 内部 (在CASE语句中)
WHEN ar.num_registered_snapshots = 0 THEN floor(ar.amount_decimal / (duration / 86400))
如果这些查询处理的任何数据库记录的duration = 0
,则表达式(duration / 86400)
将计算为零,从而在SQL执行期间导致严重的除零错误。
此漏洞的发现遵循标准的安全审查流程,从潜在的sink point开始并追溯到源头:
1_goldActiveRewards.go
,7_goldActiveODRewards.go
,11_goldActiveODOperatorSetRewards.go
)时,涉及duration
变量的除法运算被标记为潜在的除零风险。除法运算是检查此类漏洞的常见区域。duration
变量追溯到其进入sidecar系统的入口点。对事件解析逻辑(在operatorDirectedOperatorSetRewardSubmissions.go
中)的检查显示,来自传入事件数据的duration
值被直接使用,而没有任何验证以确保它为非零。duration
列被定义为bigint not null
。虽然这阻止了空值,但它明确允许值为0
。因此,零duration奖励可以被成功解析和存储。RewardsCoordinator.sol
合约(尽管超出了最初特定于sidecar的范围,但确认来源至关重要)。发现了验证逻辑require(duration % CALCULATION_INTERVAL_SECONDS == 0, ...)
。这证实了duration为0将通过链上检查,因为0对任何非零数取模都是0。require(value % interval == 0)
检查是用于各种验证的常见模式,例如确保数据对齐,检查密码scheme中的证明长度或强制执行周期性间隔。在开发和代码审查期间,此检查有时会被误读为仅确保“value是interval的倍数”,隐含地忽略了value为零的edge case。但是,此事件高亮显示了一个关键考虑因素,即在使用此模数模式时,开发人员还必须显式考虑零本身是否是特定上下文中value
的有效输入。如果value
为零(例如,如果用作除数),则下游逻辑可能会失败。因此,简单的模数检查通常是不够的,并且可能需要额外的检查,例如value > 0
(require(value > 0 && value % interval == 0)
),以防止因意外的零输入而产生的漏洞。除零错误通常会使执行过程崩溃。在这种情况下,如果sidecar遇到任何包含duration = 0的提交的奖励批次,则相应的SQL查询将失败。此故障停止了依赖于该sidecar实例的所有AVS和operator的整个奖励计算pipeline。
此漏洞带来了严重的拒绝服务(DoS)风险。通过RewardsCoordinator.sol
恶意或意外提交的零duration奖励可能会阻止sidecar处理任何进一步的奖励,从而中断生态系统的奖励分配,直到手动干预解决问题数据。鉴于诸如1_goldActiveRewards.go
和7_goldActiveODRewards.go
之类的模块已在主网上运行并处理奖励,因此潜在影响是严重的。
幸运的是,此漏洞是在审查期间由安全审计员发现的,并且在任何已知利用发生之前得到了解决。EigenLayer团队在多个层面上实施了修复:
RewardsCoordinator.sol
合约,以添加显式的链上验证,要求duration > 0
。(参见eigenlayer-contracts PR #1216).这些修复确保了零duration奖励在链上和sidecar内部都被防御性地拒绝,从而减轻了这种特定的DoS向量。主网上的EigenLayer奖励系统现在受到保护,免受此问题的影响。
- 原文链接: blog.sigmaprime.io/eigen...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!