Skip to content

Commit

Permalink
add return limit for call (#269)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmsqe committed Jun 14, 2023
1 parent 07cf2bd commit 5151e65
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 10 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ Ref: https://keepachangelog.com/en/1.0.0/

## Unreleased

### Features

* (rpc) [#1682](https://github.com/evmos/ethermint/pull/1682) Add config for maximum number of bytes returned from eth_call.

### State Machine Breaking

- (deps) [#1168](https://github.com/evmos/ethermint/pull/1716) Bump Cosmos-SDK to v0.46.11, Tendermint to v0.34.27, IAVL v0.19.5 and btcd to v0.23.4
Expand Down
20 changes: 10 additions & 10 deletions gomod2nix.toml
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,8 @@ schema = 3
version = "v0.9.1"
hash = "sha256-YLGNrHHM+mN4ElW/XWuylOnFrA/VjSY+eBuC4LN//5c="
[mod."github.com/rs/cors"]
version = "v1.8.3"
hash = "sha256-VgVB4HKAhPSjNg96mIEUN1bt5ZQng8Fi3ZABy3CDWQE="
version = "v1.9.0"
hash = "sha256-CNBCGXOydU6MIEZ0B5eXBjBm3smH2ryYHvgRJA2udBM="
[mod."github.com/rs/zerolog"]
version = "v1.27.0"
hash = "sha256-BxQtP2TROeSSpj9l1irocuSfxn55UL4ugzB/og7r8eE="
Expand Down Expand Up @@ -512,23 +512,23 @@ schema = 3
version = "v0.0.0-20230131160201-f062dba9d201"
hash = "sha256-sxLT/VOe93v0h3miChJSHS9gscTZS/B71+390ju/e20="
[mod."golang.org/x/net"]
version = "v0.8.0"
hash = "sha256-2cOtqa7aJ5mn64kZ+8+PVjJ4uGbhpXTpC1vm/+iaZzM="
version = "v0.9.0"
hash = "sha256-EG5GRDq282twyce8uugsDTjMz1pNn6zPcyVTZmSiJ14="
[mod."golang.org/x/oauth2"]
version = "v0.4.0"
hash = "sha256-Dj9wHbSbs0Ghr9Hef0hSfanaR8L0GShI18jGBT3yNn8="
[mod."golang.org/x/sync"]
version = "v0.1.0"
hash = "sha256-Hygjq9euZ0qz6TvHYQwOZEjNiTbTh1nSLRAWZ6KFGR8="
[mod."golang.org/x/sys"]
version = "v0.6.0"
hash = "sha256-zAgxiTuL24sGhbXrna9R1UYqLQh46ldztpumOScmduY="
version = "v0.7.0"
hash = "sha256-GotRHJaas/q3L+tFam0q3oQ1rc8GDStt7wnz9h8MTEU="
[mod."golang.org/x/term"]
version = "v0.6.0"
hash = "sha256-Ao0yXpwY8GyG+/23dVfJUYrfEfNUTES3RF45v1VhUAk="
version = "v0.7.0"
hash = "sha256-VYnXZ50OXTsylzncIMceVC2ZBKdbyp+V367Qbq3Vlqk="
[mod."golang.org/x/text"]
version = "v0.8.0"
hash = "sha256-hgWFnT01DRmywBEXKYEVaOee7i6z8Ydz7zGbjcWwOgI="
version = "v0.9.0"
hash = "sha256-tkhDeMsSQZr3jo7vmKehWs3DvWetwXR0IB+DCLbQ4nk="
[mod."golang.org/x/tools"]
version = "v0.7.0"
hash = "sha256-ZEjfFulQd6U9r4mEJ5RZOnW49NZnQnrCFLMKCgLg7go="
Expand Down
4 changes: 4 additions & 0 deletions rpc/backend/call_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,10 @@ func (b *Backend) DoCall(
if err != nil {
return nil, err
}
length := len(res.Ret)
if length > int(b.cfg.JSONRPC.ReturnDataLimit) && b.cfg.JSONRPC.ReturnDataLimit != 0 {
return nil, fmt.Errorf("call retuned result on length %d exceeding limit %d", length, b.cfg.JSONRPC.ReturnDataLimit)
}

if res.Failed() {
if res.VmError != vm.ErrExecutionReverted.Error() {
Expand Down
7 changes: 7 additions & 0 deletions server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ const (

// DefaultMaxOpenConnections represents the amount of open connections (unlimited = 0)
DefaultMaxOpenConnections = 0

// DefaultReturnDataLimit is maximum number of bytes returned from eth_call or similar invocations
DefaultReturnDataLimit = 100000
)

var evmTracers = []string{"json", "markdown", "struct", "access_list"}
Expand Down Expand Up @@ -138,6 +141,8 @@ type JSONRPCConfig struct {
MetricsAddress string `mapstructure:"metrics-address"`
// FixRevertGasRefundHeight defines the upgrade height for fix of revert gas refund logic when transaction reverted
FixRevertGasRefundHeight int64 `mapstructure:"fix-revert-gas-refund-height"`
// ReturnDataLimit defines maximum number of bytes returned from `eth_call` or similar invocations
ReturnDataLimit int64 `mapstructure:"return-data-limit"`
}

// TLSConfig defines the certificate and matching private key for the server.
Expand Down Expand Up @@ -241,6 +246,7 @@ func DefaultJSONRPCConfig() *JSONRPCConfig {
EnableIndexer: false,
MetricsAddress: DefaultJSONRPCMetricsAddress,
FixRevertGasRefundHeight: DefaultFixRevertGasRefundHeight,
ReturnDataLimit: DefaultReturnDataLimit,
}
}

Expand Down Expand Up @@ -351,6 +357,7 @@ func GetConfig(v *viper.Viper) (Config, error) {
EnableIndexer: v.GetBool("json-rpc.enable-indexer"),
MetricsAddress: v.GetString("json-rpc.metrics-address"),
FixRevertGasRefundHeight: v.GetInt64("json-rpc.fix-revert-gas-refund-height"),
ReturnDataLimit: v.GetInt64("json-rpc.return-data-limit"),
},
TLS: TLSConfig{
CertificatePath: v.GetString("tls.certificate-path"),
Expand Down
3 changes: 3 additions & 0 deletions server/config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ metrics-address = "{{ .JSONRPC.MetricsAddress }}"
# Upgrade height for fix of revert gas refund logic when transaction reverted.
fix-revert-gas-refund-height = {{ .JSONRPC.FixRevertGasRefundHeight }}
# Maximum number of bytes returned from eth_call or similar invocations.
return-data-limit = {{ .JSONRPC.ReturnDataLimit }}
###############################################################################
### TLS Configuration ###
###############################################################################
Expand Down
1 change: 1 addition & 0 deletions server/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const (
// https://github.com/ethereum/go-ethereum/blob/master/metrics/metrics.go#L35-L55
JSONRPCEnableMetrics = "metrics"
JSONRPCFixRevertGasRefundHeight = "json-rpc.fix-revert-gas-refund-height"
JSONRPCReturnDataLimit = "json-rpc.return-data-limit"
)

// EVM flags
Expand Down
11 changes: 11 additions & 0 deletions tests/integration_tests/configs/exploit.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
local config = import 'default.jsonnet';

config {
'ethermint_9000-1'+: {
'app-config'+: {
'json-rpc'+: {
'return-data-limit': 3594241, // memory_byte_size + 1
},
},
},
}
10 changes: 10 additions & 0 deletions tests/integration_tests/hardhat/contracts/TestExploitContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract TestExploitContract {
function dos() public pure {
assembly {
return(0, 0x36d800)
}
}
}
75 changes: 75 additions & 0 deletions tests/integration_tests/test_exploit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

import pytest
import requests
from pystarport import ports

from .network import setup_custom_ethermint
from .utils import CONTRACTS, deploy_contract


@pytest.fixture(scope="module")
def custom_ethermint(tmp_path_factory):
path = tmp_path_factory.mktemp("exploit")
yield from setup_custom_ethermint(
path, 26910, Path(__file__).parent / "configs/exploit.jsonnet"
)


def call(port, params):
url = f"http://127.0.0.1:{ports.evmrpc_port(port)}"
rsp = requests.post(url, json=params)
assert rsp.status_code == 200
return rsp.json()


def run_test(provider, concurrent, batch, expect_cb):
_, res = deploy_contract(provider.w3, CONTRACTS["TestExploitContract"])
param = {
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"data": "0x5e67164c",
"to": res["contractAddress"],
},
"latest",
],
"id": 1,
}
params = []
for _ in range(batch):
params.append(param)
with ThreadPoolExecutor(concurrent) as executor:
tasks = [
executor.submit(call, provider.base_port(0), params)
for _ in range(0, concurrent)
]
results = [future.result() for future in as_completed(tasks)]
assert len(results) == concurrent
for result in results:
expect_cb(result)


def test_call(ethermint):
concurrent = 2
batch = 1

def expect_cb(result):
for item in result:
assert "error" in item
assert "exceeding limit" in item["error"]["message"]

run_test(ethermint, concurrent, batch, expect_cb)


def test_large_call(custom_ethermint):
concurrent = 2
batch = 1

def expect_cb(result):
for item in result:
assert "error" not in item

run_test(custom_ethermint, concurrent, batch, expect_cb)
1 change: 1 addition & 0 deletions tests/integration_tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"TestChainID": "ChainID.sol",
"Mars": "Mars.sol",
"StateContract": "StateContract.sol",
"TestExploitContract": "TestExploitContract.sol",
}


Expand Down

0 comments on commit 5151e65

Please sign in to comment.