Rootstock Labs | 报告 #2489843 - 由于低效的CODESIZE实现,精心构造的智能合约执行时间可达1.5分钟
时间线
- guido 向Rootstock Labs提交报告 - 2024年5月3日晚上8:23(UTC)
- Rootstock Labs团队确认问题有效性并将严重性从高调整为中 - 2024年5月8日
- 漏洞修复补丁被合并 - 2024年5月29日
- 奖励发放 - 2024年6月13日
- 报告公开披露 - 2025年6月12日
摘要
TLDR: VM.doCODESIZE() 效率低下,可能导致执行缓慢。
VM.doCODESIZE() 通过调用 Program.getCode() 然后对返回值调用 length 属性来获取代码大小:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
protected void doCODESIZE() {
if (computeGas) {
if (op == OpCode.EXTCODESIZE) {
gasCost = GasCost.EXT_CODE_SIZE;
}
spendOpCodeGas();
}
// 执行阶段
DataWord codeLength;
if (op == OpCode.CODESIZE) {
codeLength = DataWord.valueOf(program.getCode().length); // 在初始化期间将返回初始化代码大小
}
}
|
这意味着每次调用 CODESIZE 时,program.getCode() 都会返回整个字节数组。这一特性可被利用来在 Program.getCode() 和 VM.doCODESIZE() 之间传输大量数据,如下面的重现器中总计超过1TB的数据,这使得执行非常缓慢。
重现步骤
重现器(在Linux x64上测试):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
wget -q https://download.java.net/java/GA/jdk11/9/GPL/openjdk-11.0.2_linux-x64_bin.tar.gz
tar zxf openjdk-11.0.2_linux-x64_bin.tar.gz
export JAVA_HOME=$(realpath jdk-11.0.2/)
git clone --depth 1 https://github.com/rsksmart/rskj.git
cd rskj/
echo "task testJar(type: Jar) {" >>rskj-core/build.gradle
echo " from sourceSets.test.output" >>rskj-core/build.gradle
echo " classifier = 'tests'" >>rskj-core/build.gradle
echo "}" >>rskj-core/build.gradle
echo "assemble.dependsOn(testJar)" >>rskj-core/build.gradle
./configure.sh
# 构建rskj
./gradlew assemble
# 构造重现器
echo """
[完整的Java PoC代码]
""" >Poc.java
# 构建重现器
$JAVA_HOME/bin/javac -cp rskj-core/build/libs/rskj-core-6.2.0-SNAPSHOT-tests.jar:rskj-core/build/libs/rskj-core-6.2.0-SNAPSHOT-all.jar Poc.java
# 运行重现器
time $JAVA_HOME/bin/java -cp .:rskj-core/build/libs/rskj-core-6.2.0-SNAPSHOT-tests.jar:rskj-core/build/libs/rskj-core-6.2.0-SNAPSHOT-all.jar Poc
|
在我的机器上,这需要1分29.355秒才能运行。
此重现器是使用遗传算法构建的,它不一定代表复制的字节数的全局最大值(也就是说,很可能可以构造更慢的示例)。
修复方案
通过实现一个 getCodeLength() 方法在 Program 中,并从 VM.doCODESIZE() 调用此方法,可以轻松完全缓解此问题,这样只传输代码长度而不是整个代码数组。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
diff --git a/rskj-core/src/main/java/org/ethereum/vm/VM.java b/rskj-core/src/main/java/org/ethereum/vm/VM.java
index 2365d38..88eb90d 100644
--- a/rskj-core/src/main/java/org/ethereum/vm/VM.java
+++ b/rskj-core/src/main/java/org/ethereum/vm/VM.java
@@ -775,7 +775,7 @@ public class VM {
// EXECUTION PHASE
DataWord codeLength;
if (op == OpCode.CODESIZE) {
- codeLength = DataWord.valueOf(program.getCode().length); // during initialization it will return the initialization code size
+ codeLength = DataWord.valueOf(program.getCodeLength()); // during initialization it will return the initialization code size
} else {
DataWord address = program.stackPop();
codeLength = DataWord.valueOf(program.getCodeLengthAt(address));
diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java
index 5a79952..35885d5 100644
--- a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java
+++ b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java
@@ -960,6 +960,10 @@ public class Program {
return Arrays.copyOf(ops, ops.length);
}
+ public int getCodeLength() {
+ return ops.length;
+ }
+
public Keccak256 getCodeHashAt(RskAddress addr, boolean standard) {
if(standard) {
return invoke.getRepository().getCodeHashStandard(addr);
|
应用此补丁后,执行时间仅为0分0.755秒,速度提高了118倍以上。
影响
摘要: 可能导致网络停滞。
测试环境
在Linux x64(Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz)上测试,使用提交a61e9080ba353fcbb6f94cf4606986a88c3043e3的rskj。
严重性评估
Rootstock Labs团队将严重性从高调整为中,提供的PoC在现代硬件上使用670万gas处理需要长达30秒。他们认为所涉及的风险不是高而是中。可利用性并不直接,要对网络产生重大影响,攻击必须持续很长时间。
修复状态
修复补丁已合并到代码库:https://github.com/rsksmart/rskj/pull/2365
报告信息
- 报告ID: #2489843
- 状态: 已解决
- 严重性: 中 (4 ~ 6.9)
- 弱点: 不受控制的资源消耗
- CVE ID: 无
- 奖励: 已发放