Skip to content

Commit 446653d

Browse files
committed
EVMScripts: check byte lengths, avoid returning unallocated memory, and forward error data (#496)
1 parent 2bfd50a commit 446653d

13 files changed

+845
-362
lines changed

contracts/evmscript/EVMScriptRegistry.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ contract EVMScriptRegistry is IEVMScriptRegistry, EVMScriptRegistryConstants, Ar
1919
// WARN: Manager can censor all votes and the like happening in an org
2020
bytes32 public constant REGISTRY_MANAGER_ROLE = 0xf7a450ef335e1892cb42c8ca72e7242359d7711924b75db5717410da3f614aa3;
2121

22+
uint256 internal constant SCRIPT_START_LOCATION = 4;
23+
2224
string private constant ERROR_INEXISTENT_EXECUTOR = "EVMREG_INEXISTENT_EXECUTOR";
2325
string private constant ERROR_EXECUTOR_ENABLED = "EVMREG_EXECUTOR_ENABLED";
2426
string private constant ERROR_EXECUTOR_DISABLED = "EVMREG_EXECUTOR_DISABLED";
27+
string private constant ERROR_SCRIPT_LENGTH_TOO_SHORT = "EVMREG_SCRIPT_LENGTH_TOO_SHORT";
2528

2629
struct ExecutorEntry {
2730
IEVMScriptExecutor executor;
@@ -96,6 +99,7 @@ contract EVMScriptRegistry is IEVMScriptRegistry, EVMScriptRegistryConstants, Ar
9699
* @param _script EVMScript being inspected
97100
*/
98101
function getScriptExecutor(bytes _script) public view returns (IEVMScriptExecutor) {
102+
require(_script.length >= SCRIPT_START_LOCATION, ERROR_SCRIPT_LENGTH_TOO_SHORT);
99103
uint256 id = _script.getSpecId();
100104

101105
// Note that we don't need to check for an executor's existence in this case, as only

contracts/evmscript/EVMScriptRunner.sol

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import "../common/Initializable.sol";
1414

1515
contract EVMScriptRunner is AppStorage, Initializable, EVMScriptRegistryConstants, KernelNamespaceConstants {
1616
string private constant ERROR_EXECUTOR_UNAVAILABLE = "EVMRUN_EXECUTOR_UNAVAILABLE";
17-
string private constant ERROR_EXECUTION_REVERTED = "EVMRUN_EXECUTION_REVERTED";
1817
string private constant ERROR_PROTECTED_STATE_MODIFIED = "EVMRUN_PROTECTED_STATE_MODIFIED";
1918

2019
event ScriptResult(address indexed executor, bytes script, bytes input, bytes returnData);
@@ -34,36 +33,51 @@ contract EVMScriptRunner is AppStorage, Initializable, EVMScriptRegistryConstant
3433
protectState
3534
returns (bytes)
3635
{
37-
// TODO: Too much data flying around, maybe extracting spec id here is cheaper
3836
IEVMScriptExecutor executor = getEVMScriptExecutor(_script);
3937
require(address(executor) != address(0), ERROR_EXECUTOR_UNAVAILABLE);
4038

4139
bytes4 sig = executor.execScript.selector;
4240
bytes memory data = abi.encodeWithSelector(sig, _script, _input, _blacklist);
43-
require(address(executor).delegatecall(data), ERROR_EXECUTION_REVERTED);
4441

45-
bytes memory output = returnedDataDecoded();
46-
47-
emit ScriptResult(address(executor), _script, _input, output);
42+
bytes memory output;
43+
assembly {
44+
let success := delegatecall(
45+
gas, // forward all gas
46+
executor, // address
47+
add(data, 0x20), // calldata start
48+
mload(data), // calldata length
49+
0, // don't write output (we'll handle this ourselves)
50+
0 // don't write output
51+
)
4852

49-
return output;
50-
}
53+
output := mload(0x40) // free mem ptr get
5154

52-
/**
53-
* @dev copies and returns last's call data. Needs to ABI decode first
54-
*/
55-
function returnedDataDecoded() internal pure returns (bytes ret) {
56-
assembly {
57-
let size := returndatasize
58-
switch size
59-
case 0 {}
55+
switch success
56+
case 0 {
57+
// If the call errored, forward its full error data
58+
returndatacopy(output, 0, returndatasize)
59+
revert(output, returndatasize)
60+
}
6061
default {
61-
ret := mload(0x40) // free mem ptr get
62-
mstore(0x40, add(ret, add(size, 0x20))) // free mem ptr set
63-
returndatacopy(ret, 0x20, sub(size, 0x20)) // copy return data
62+
// Copy result
63+
//
64+
// Needs to perform an ABI decode for the expected `bytes` return type of
65+
// `executor.execScript()` as solidity will automatically ABI encode the returned bytes as:
66+
// [ position of the first dynamic length return value = 0x20 (32 bytes) ]
67+
// [ output length (32 bytes) ]
68+
// [ output content (N bytes) ]
69+
//
70+
// Perform the ABI decode by ignoring the first 32 bytes of the return data
71+
let copysize := sub(returndatasize, 0x20)
72+
returndatacopy(output, 0x20, copysize)
73+
74+
mstore(0x40, add(output, copysize)) // free mem ptr set
6475
}
6576
}
66-
return ret;
77+
78+
emit ScriptResult(address(executor), _script, _input, output);
79+
80+
return output;
6781
}
6882

6983
modifier protectState {

contracts/evmscript/executors/CallsScript.sol

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ contract CallsScript is BaseEVMScriptExecutor {
1616

1717
string private constant ERROR_BLACKLISTED_CALL = "EVMCALLS_BLACKLISTED_CALL";
1818
string private constant ERROR_INVALID_LENGTH = "EVMCALLS_INVALID_LENGTH";
19+
20+
/* This is manually crafted in assembly
1921
string private constant ERROR_CALL_REVERTED = "EVMCALLS_CALL_REVERTED";
22+
*/
2023

2124
event LogScriptCall(address indexed sender, address indexed src, address indexed dst);
2225

@@ -25,14 +28,17 @@ contract CallsScript is BaseEVMScriptExecutor {
2528
* @param _script [ specId (uint32) ] many calls with this structure ->
2629
* [ to (address: 20 bytes) ] [ calldataLength (uint32: 4 bytes) ] [ calldata (calldataLength bytes) ]
2730
* @param _blacklist Addresses the script cannot call to, or will revert.
28-
* @return always returns empty byte array
31+
* @return Always returns empty byte array
2932
*/
3033
function execScript(bytes _script, bytes, address[] _blacklist) external isInitialized returns (bytes) {
3134
uint256 location = SCRIPT_START_LOCATION; // first 32 bits are spec id
3235
while (location < _script.length) {
36+
// Check there's at least address + calldataLength available
37+
require(_script.length - location >= 0x18, ERROR_INVALID_LENGTH);
38+
3339
address contractAddress = _script.addressAt(location);
3440
// Check address being called is not blacklist
35-
for (uint i = 0; i < _blacklist.length; i++) {
41+
for (uint256 i = 0; i < _blacklist.length; i++) {
3642
require(contractAddress != _blacklist[i], ERROR_BLACKLISTED_CALL);
3743
}
3844

@@ -50,11 +56,43 @@ contract CallsScript is BaseEVMScriptExecutor {
5056

5157
bool success;
5258
assembly {
53-
success := call(sub(gas, 5000), contractAddress, 0, calldataStart, calldataLength, 0, 0)
54-
}
59+
success := call(
60+
sub(gas, 5000), // forward gas left - 5000
61+
contractAddress, // address
62+
0, // no value
63+
calldataStart, // calldata start
64+
calldataLength, // calldata length
65+
0, // don't write output
66+
0 // don't write output
67+
)
5568

56-
require(success, ERROR_CALL_REVERTED);
69+
switch success
70+
case 0 {
71+
let ptr := mload(0x40)
72+
73+
switch returndatasize
74+
case 0 {
75+
// No error data was returned, revert with "EVMCALLS_CALL_REVERTED"
76+
// See remix: doing a `revert("EVMCALLS_CALL_REVERTED")` always results in
77+
// this memory layout
78+
mstore(ptr, 0x08c379a000000000000000000000000000000000000000000000000000000000) // error identifier
79+
mstore(add(ptr, 0x04), 0x0000000000000000000000000000000000000000000000000000000000000020) // starting offset
80+
mstore(add(ptr, 0x24), 0x0000000000000000000000000000000000000000000000000000000000000016) // reason length
81+
mstore(add(ptr, 0x44), 0x45564d43414c4c535f43414c4c5f524556455254454400000000000000000000) // reason
82+
83+
revert(ptr, 100) // 100 = 4 + 3 * 32 (error identifier + 3 words for the ABI encoded error)
84+
}
85+
default {
86+
// Forward the full error data
87+
returndatacopy(ptr, 0, returndatasize)
88+
revert(ptr, returndatasize)
89+
}
90+
}
91+
default { }
92+
}
5793
}
94+
// No need to allocate empty bytes for the return as this can only be called via an delegatecall
95+
// (due to the isInitialized modifier)
5896
}
5997

6098
function executorType() external pure returns (bytes32) {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
pragma solidity 0.4.24;
2+
3+
import "../../apps/AragonApp.sol";
4+
5+
6+
contract AppStubScriptRunner is AragonApp {
7+
event ReturnedBytes(bytes returnedBytes);
8+
9+
// Initialization is required to access any of the real executors
10+
function initialize() public {
11+
initialized();
12+
}
13+
14+
function runScript(bytes script) public returns (bytes) {
15+
bytes memory returnedBytes = runScript(script, new bytes(0), new address[](0));
16+
emit ReturnedBytes(returnedBytes);
17+
return returnedBytes;
18+
}
19+
20+
function runScriptWithBan(bytes script, address[] memory blacklist) public returns (bytes) {
21+
bytes memory returnedBytes = runScript(script, new bytes(0), blacklist);
22+
emit ReturnedBytes(returnedBytes);
23+
return returnedBytes;
24+
}
25+
26+
function runScriptWithIO(bytes script, bytes input, address[] memory blacklist) public returns (bytes) {
27+
bytes memory returnedBytes = runScript(script, input, blacklist);
28+
emit ReturnedBytes(returnedBytes);
29+
return returnedBytes;
30+
}
31+
32+
function runScriptWithNewBytesAllocation(bytes script) public returns (bytes) {
33+
bytes memory returnedBytes = runScript(script, new bytes(0), new address[](0));
34+
bytes memory newBytes = new bytes(64);
35+
36+
// Fill in new bytes array with some dummy data to let us check it doesn't corrupt the
37+
// script's returned bytes
38+
uint256 first = uint256(keccak256("test"));
39+
uint256 second = uint256(keccak256("mock"));
40+
assembly {
41+
mstore(add(newBytes, 0x20), first)
42+
mstore(add(newBytes, 0x40), second)
43+
}
44+
emit ReturnedBytes(returnedBytes);
45+
return returnedBytes;
46+
}
47+
48+
/*
49+
function getActionsCount(bytes script) public constant returns (uint256) {
50+
return getScriptActionsCount(script);
51+
}
52+
53+
function getAction(bytes script, uint256 i) public constant returns (address, bytes) {
54+
return getScriptAction(script, i);
55+
}
56+
*/
57+
}

contracts/test/mocks/EVMScriptExecutorMock.sol

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import "../../evmscript/executors/BaseEVMScriptExecutor.sol";
66
contract EVMScriptExecutorMock is BaseEVMScriptExecutor {
77
bytes32 internal constant EXECUTOR_TYPE = keccak256("MOCK_SCRIPT");
88

9-
function execScript(bytes, bytes, address[]) external isInitialized returns (bytes) {
9+
function execScript(bytes _script, bytes, address[]) external isInitialized returns (bytes) {
10+
// Return full input script if it's more than just the spec ID, otherwise return an empty
11+
// bytes array
12+
if (_script.length > SCRIPT_START_LOCATION) {
13+
return _script;
14+
}
1015
}
1116

1217
function executorType() external pure returns (bytes32) {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
pragma solidity 0.4.24;
2+
3+
4+
import "../../evmscript/executors/BaseEVMScriptExecutor.sol";
5+
6+
contract EVMScriptExecutorRevertMock is BaseEVMScriptExecutor {
7+
string public constant ERROR_MOCK_REVERT = "MOCK_REVERT";
8+
bytes32 internal constant EXECUTOR_TYPE = keccak256("MOCK_REVERT_SCRIPT");
9+
10+
function execScript(bytes, bytes, address[]) external isInitialized returns (bytes) {
11+
revert(ERROR_MOCK_REVERT);
12+
}
13+
14+
function executorType() external pure returns (bytes32) {
15+
return EXECUTOR_TYPE;
16+
}
17+
}

contracts/test/mocks/ExecutionTarget.sol

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@ pragma solidity 0.4.24;
22

33

44
contract ExecutionTarget {
5+
string public constant ERROR_EXECUTION_TARGET = "EXECUTION_TARGET_REVERTED";
56
uint public counter;
67

78
function execute() public {
89
counter += 1;
910
emit Executed(counter);
1011
}
1112

12-
function failExecute() public pure {
13-
revert();
13+
function failExecute(bool errorWithData) public pure {
14+
if (errorWithData) {
15+
revert(ERROR_EXECUTION_TARGET);
16+
} else {
17+
revert();
18+
}
1419
}
1520

1621
function setCounter(uint x) public {

contracts/test/mocks/MockScriptExecutorApp.sol

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)