EVM(以太坊虚拟机)兼容的区块链旨在与以太坊区块链的智能合约功能、编程语言(Solidity)和工具生态系统兼容。在此过程中,实现一个符合EVM的虚拟机是关键步骤之一。然而,在我们的研究中,我们发现要保持不同实现中EVM的兼容性并非易事。
为解决此问题,BlockSec开发了一个内部系统,可以系统地定位EVM实现中的错误和安全漏洞。该系统已被证明有效。它报告了Aurora Engine中的四个错误和Moonbeam中的四个错误。所有这些错误都已报告并修复。值得注意的是,去年这种测试方法也被用于定位Solana rbpf实现中的两个关键漏洞(CVE-2021–46102,CVE-2022–23066)。
1. 背景
如今,许多不同的区块链被提出,旨在优化低gas费、高吞吐量和卓越的性能。在这种情况下,新的虚拟机或开发语言被提出,这增加了对原始Solidity开发者来说转换的门槛。在这种情况下,许多EVM兼容的解决方案被提出,允许用户在这些新链上部署他们用Solidity开发的DApp。Aurora和Moonbeam是这些EVM兼容解决方案的代表。然而,这些EVM实现的健壮性、可靠性和精确性未知,值得我们关注。为此,我们利用差分模糊测试技术,并检查实现中是否存在任何缺陷。
2. 差分模糊测试
基本思想是将相同的输入馈送到这些EVM实现(例如,Aurora、Moonbeam)和最先进的以太坊客户端(即geth),以检查它们是否产生相同的输出。具体来说,我们收集以太坊上的历史交易,并使用不同的策略来突变合约代码和交易状态,以生成测试用例。我们的发现表明,Aurora Engine和Moonbeam在某些情况下不符合规范。幸运的是,所有报告的问题都已得到解决,现在我们将分享详细信息。
3. 发现的错误
大多数这些错误存在于预编译合约中,其根本原因和影响各不相同。例如,一些错误会影响nonce值的计算,而另一些则会影响gas计算,并可能导致DoS攻击。所有发现的错误都违反了EVM规范的指定执行逻辑,并可能在特定情况下导致意外行为。请在下面找到发现错误的详细描述。
3.1 不当验证
密码学逻辑复杂。在这种情况下,在EVM字节码中实现相应的逻辑可能会导致相当大的gas消耗。相反,用原生代码编写的预编译合约可以提高性能。然而,由于不当验证,我们在预编译合约中发现了一些错误。
Aurora Engine:ecPairing
此错误在预编译合约 ecPairing 中发现。
ecPairing 的输入包含两条椭圆曲线上的多个点。根据规范,点 (0, 0) 存在于两条曲线上,并且应该是有效的输入:


然而,Aurora Engine(版本2.7.0)在输入中包含点 (0,0) 时会回滚。

相关PR在此:here。
Moonbeam:ecMul
此错误在预编译合约 ecMul 中。与Aurora Engine不同,Moonbeam在输入有效时回滚交易。根据规范,预编译合约 ecMul 应该用零填充输入,当输入长度小于64时。然而,Moonbeam会回滚而不是进行填充操作。

我们还发现预编译合约 ecAdd 和 modexp 存在相同的问题。
相关PR在此:here。
Moonbeam:ecRecover
此错误在预编译合约 ecRecover 中,用于恢复以太坊地址。
根据规范,input[32..63] v 代表一个U256标识符,其值应为27或28,否则 ecRecover 不应返回任何内容(但整个交易不应回滚)。
然而,Moonbeam在这里犯了两个错误:
- 它只检查input[63],而不是将input[32..63]转换为U256类型然后检查值。
- 当标识符为0或1时,输入被认为是有效的(而它应该只有27或28)。

相关PR在此:here。
Moonbeam:ecPairing
此错误在预编译合约 ecPairing 中。当输入无效时,Moonbeam不会回滚交易。根据规范,预编译合约 ecPairing 的输入应该是192的倍数。否则,交易应回滚。

然而,当上述要求不满足时,Moonbeam不会回滚交易。
相关PR在此:here。
3.2 不正确的gas计算
每个预编译合约都有一个确定gas使用量的算法。不正确的gas计算可能导致DoS攻击。
然而,我们在Aurora Engine和Moonbeam中都发现了两个gas计算不正确的错误。
Aurora Engine:modexp
错误存在于预编译合约 modexp 中。gas使用量的计算算法由EIP-2565定义。Gas使用量与迭代次数有关。
计算迭代次数的算法如下。
def calculate_iteration_count(exponent_length, exponent):
iteration_count = 0
if exponent_length <= 32 and exponent == 0: iteration_count = 0
elif exponent_length <= 32: iteration_count = exponent.bit_length() - 1
elif exponent_length > 32: iteration_count = (8 * (exponent_length - 32)) + ((exponent & (2**256 - 1)).bit_length() - 1)
return max(iteration_count, 1)
根据上述算法,迭代次数至少为一。然而,Aurora Engine直接返回 iteration_count 而不是 max(iteration_count, 1)。在这种情况下,返回值(即 iteration_count)可能为0,这意味着在特定情况下,Aurora会收取比预期少得多的gas费。
相关问题链接在此:here。
Moonbeam:modexp
错误也存在于预编译合约 modexp 的 calculate_iteration_count 函数中。但在Moonbeam中发现。
当 exponent_length 大于32时,iteration_count 使用以下算法计算。
(8 * (exponent_length - 32)) + ((exponent & (2**256 - 1)).bit_length() - 1)
请注意,它使用了 exponent & (2**256 - 1),它取 exponent 的最低32个字节,Moonbeam的实现遵循此算法。
然而,根据规范,gas计算公式应使用 exponent 的最高32个字节:

相关PR在此:here。
3.3 Nonce增量错误
外部拥有账户(EOA)的nonce表示该地址成功签名的交易数量。然而,我们注意到在Aurora Engine上,可以通过发送无效交易来增加nonce。
根据EIP-1559,在执行交易之前,EVM应确保签名者有足够的余额来支付转移的本地代币(例如ETH)和所需的gas。否则,交易应被丢弃,并且签名者的nonce不应增加。

尽管Aurora Engine在此情况下丢弃了交易,但它仍然增加了签名者的nonce。
相关PR在此:here。
3.4 不正确的opcode实现
此错误涉及特定opcode(即PUSH)的实现。当PUSH opcode后面的字节不完整时,它们应该右对齐。例如,字节码 0x64ffff 可以解码为:
PUSH5 0xffff
由于操作数应该是右对齐的,值 0xffff000000 应该被推送到堆栈上。然而,Aurora Engine和Moonbeam中的EVM实现都推送了 0xffff,这是不正确的。
相关PR在此:here。
4. 我们的服务
在BlockSec,我们理解在不同的区块链实现中保持EVM兼容性和安全性的重要性。这就是为什么我们开发了一种系统的错误检测方法,可以轻松地定位EVM实现中的漏洞。
我们的内部系统已被证明在定位EVM实现中的错误和安全漏洞方面非常有效。它已成功报告并修复了Aurora Engine中的四个错误和Moonbeam中的四个错误。此外,我们的测试方法去年被用于定位Solana rbpf实现中的两个关键漏洞(CVE-2021–46102,CVE-2022–23066),突显了我们方法的有效性。
通过实施我们系统的错误检测方法,我们的客户可以确信他们的EVM实现是安全可靠的。我们致力于为EVM兼容性和安全性提供一流的解决方案,帮助我们的客户赢得用户和利益相关者的信任。
关于BlockSec
BlockSec团队专注于区块链生态系统的安全性,并与领先的DeFi项目合作以保障其产品安全。该团队由学术界和工业界顶尖的安全研究人员和经验丰富的专家创立。他们在著名的会议上发表了多篇区块链安全论文,报告了多个DeFi应用的零日攻击,并发布了高影响力安全事件的详细分析报告。



