数字变武器:Osmosis数学库中的DoS漏洞

本文披露了Osmosis链中的一个漏洞,攻击者可利用该漏洞通过精心构造的交易占用大量计算时间,从而停止整个链的运行。漏洞存在于数学库的指数函数近似计算中,通过限制循环迭代次数已修复该问题。

数字变武器:Osmosis数学库中的DoS漏洞

Trail of Bits公开披露了Osmosis链中的一个漏洞,攻击者可以构造一种交易,使其在Osmosis节点上占用的计算时间与消耗的gas量不成比例。利用该漏洞,攻击者可通过向验证者发送大量此类交易来停止Osmosis链的运行。在我们向Osmosis开发团队报告此漏洞后,他们通过硬分叉修复了该问题,避免了攻击发生。

Osmosis是一个具有原生交换池功能的Cosmos链。用户每天在Osmosis的池中交换数十万美元的价值。自然,这些池需要执行大量相当精确的计算,而这正是我们发现的漏洞所在。

漏洞详情

我们在Osmosis的数学库中发现了这个漏洞,该库用于提供数学函数的近似解。具体来说,该漏洞影响了他们的指数函数。他们使用泰勒级数近似来计算a^b:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 计算a^b
// 假设:a在0到2之间b在0到1之间
fn PowApprox(a,b) {
  total <- 1
  i <- 0
  term <- 1
  const precision = 0.00000001
  // (实际实现中将precision作为函数参数而非常量)
  while abs(term) >= precision {
    i <- i + 1
    term <- term * ((b-(i-1)) / i) * (a-1)
    total <- total + term
  }
  return total
}

然而,这个实现存在一个问题。while循环会一直运行直到term足够小,但没有设置最大迭代次数的限制。如果我们精心选择a和b的值,可以使这个循环需要非常多的迭代才能终止。特别是,使用PowApprox计算1.99999999999999^0.1需要超过200万次迭代,在M1处理器上运行超过800毫秒。

这种长时间运行在调用PowApprox函数的交易gas成本中并未考虑。这意味着如果攻击者能够构造一个调用PowApprox(1.99999999999999, 0.1)的交易,他们可以在Osmosis节点上占用近一秒的运行时间,而只需支付很少的gas费用。通过反复执行此操作,他们可以使整个链停止运行。

幸运的是(对攻击者而言),这样的交易确实存在。在以下代码片段中有一个对PowApprox的调用,该代码用于计算当有人向交换池存入代币时应给予的份额:

1
2
3
shares_to_give = current_total_shares * (1 -
PowApprox(((current_total_tokens + tokens_added) /
current_total_tokens), token_weight))

因此,如果攻击者创建一个tokenA权重为0.1的池,用1.0 tokenA初始化它,然后存入0.99999999999999更多的tokenA,他们就可以触发PowApprox中的长时间计算。通过反复存入和提取这0.99999999999999 tokenA,攻击者可以使Osmosis节点反复计算PowApprox,从而停止整个链的运行!

简单解决方案

幸运的是,这个问题的修复非常简单:限制循环迭代次数,如果达到限制就回滚交易。Osmosis最近的硬分叉推送了这个修复,防止了攻击。至于如何防止类似漏洞在其他地方出现,我们的建议很简单:模糊测试。使用gofuzz以100ms超时测试PowApprox函数会很快发现这个漏洞。使用10ms超时替代时,Go的原生模糊测试器也能检测到这个漏洞。

我们于2023年9月6日向Osmosis团队报告了这个漏洞。包含修复的PR于2023年10月6日合并,应用此修复的硬分叉于2023年10月23日执行。

我们要感谢Osmosis团队迅速与我们合作解决这些问题。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计