Skip to content

Commit

Permalink
Merge pull request #255 from aragon/evmscript-min-return
Browse files Browse the repository at this point in the history
Require minimum return data from EVMScript executors to prevent selfdestructs
  • Loading branch information
izqui committed Mar 27, 2018
2 parents 1f167c1 + 3a92325 commit b4b45b8
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 11 deletions.
8 changes: 6 additions & 2 deletions contracts/evmscript/EVMScriptRunner.sol
Expand Up @@ -20,7 +20,11 @@ contract EVMScriptRunner is AppStorage, EVMScriptRegistryConstants {

require(executorAddr.delegatecall(sig, calldataArgs));

return returnedDataDecoded();
bytes memory ret = returnedDataDecoded();

require(ret.length > 0);

return ret;
}

function getExecutor(bytes _script) public view returns (IEVMScriptExecutor) {
Expand Down Expand Up @@ -57,4 +61,4 @@ contract EVMScriptRunner is AppStorage, EVMScriptRegistryConstants {
require(kernel == preKernel);
require(appId == preAppId);
}
}
}
5 changes: 5 additions & 0 deletions contracts/evmscript/executors/CallsScript.sol
Expand Up @@ -47,5 +47,10 @@ contract CallsScript is IEVMScriptExecutor {
switch success case 0 { revert(0, 0) }
}
}

bytes memory ret = new bytes(1);
ret[0] = 1;

return ret;
}
}
10 changes: 7 additions & 3 deletions contracts/evmscript/executors/DelegateScript.sol
Expand Up @@ -5,7 +5,7 @@ import "../IEVMScriptExecutor.sol";


interface DelegateScriptTarget {
function exec() public;
function exec() public returns (bool);
}


Expand Down Expand Up @@ -35,7 +35,11 @@ contract DelegateScript is IEVMScriptExecutor {
function delegate(address _addr, bytes memory _input) internal returns (bytes memory output) {
require(isContract(_addr));
require(_addr.delegatecall(_input.length > 0 ? _input : defaultInput()));
return returnedData();
bytes memory ret = returnedData();

require(ret.length > 0);

return ret;
}

function isContract(address _target) internal view returns (bool) {
Expand All @@ -61,4 +65,4 @@ contract DelegateScript is IEVMScriptExecutor {
}
return ret;
}
}
}
37 changes: 36 additions & 1 deletion test/evm_script.js
Expand Up @@ -7,7 +7,9 @@ const ExecutionTarget = artifacts.require('ExecutionTarget')
const Executor = artifacts.require('Executor')
const Delegator = artifacts.require('Delegator')
const FailingDelegator = artifacts.require('FailingDelegator')
const DyingDelegator = artifacts.require('DyingDelegator')
const FailingDeployment = artifacts.require('FailingDeployment')
const MockDyingScriptExecutor = artifacts.require('MockDyingScriptExecutor')

const Kernel = artifacts.require('Kernel')
const ACL = artifacts.require('ACL')
Expand Down Expand Up @@ -71,6 +73,8 @@ contract('EVM Script', accounts => {
const receipt = await dao.newAppInstance(executorAppId, '0x0', { from: boss })
executor = Executor.at(receipt.logs.filter(l => l.event == 'NewAppProxy')[0].args.proxy)
executionTarget = await ExecutionTarget.new()

await acl.grantPermission(boss, reg.address, await reg.REGISTRY_MANAGER_ROLE(), { from: boss })
})

it('fails to execute if spec ID is 0', async () => {
Expand All @@ -86,7 +90,6 @@ contract('EVM Script', accounts => {
})

it('can disable executors', async () => {
await acl.grantPermission(boss, reg.address, await reg.REGISTRY_MANAGER_ROLE(), { from: boss })
await reg.disableScriptExecutor(1, { from: boss })
return assertRevert(async () => {
await executor.execute(encodeCallScript([]))
Expand Down Expand Up @@ -205,6 +208,14 @@ contract('EVM Script', accounts => {
})
})

it('fails if underlying call selfdestructs', async () => {
const dyingDelegator = await DyingDelegator.new()
return assertRevert(async () => {
// extra gas to avoid oog
await executor.executeWithBan(encodeDelegate(dyingDelegator.address), [], { gas: 2e6 })
})
})

it('fails if calling to non-contract', async () => {
return assertRevert(async () => {
await executor.execute(encodeDelegate(accounts[0])) // addr is too small
Expand Down Expand Up @@ -280,6 +291,30 @@ contract('EVM Script', accounts => {
})
})

context('adding mock dying executor', () => {
let dyingExecutor = null

beforeEach(async () => {
dyingExecutor = await MockDyingScriptExecutor.new()
await reg.addScriptExecutor(dyingExecutor.address, { from: boss })
})

it('registers new executor', async () => {
assert.equal(await reg.getScriptExecutor('0x00000004'), dyingExecutor.address)
})

it('executes mock executor', async () => {
await executor.execute('0x00000004')
})

it('fails when executor selfdestructs', async () => {
return assertRevert(async () => {
// passing some input makes executor to selfdestruct
await executor.executeWithIO('0x00000004', '0x0001', [])
})
})
})

context('script overflow', async () => {
const encodeCallScriptBad = actions => {
return actions.reduce((script, { to, calldata }) => {
Expand Down
14 changes: 9 additions & 5 deletions test/mocks/Delegator.sol
Expand Up @@ -5,15 +5,20 @@ import "./Executor.sol";


contract Delegator is ExecutorStorage, DelegateScriptTarget {
function exec() public {
function exec() public returns (bool) {
randomNumber += 1234;
}

function execReturnValue(uint i) public pure returns (uint) { return i; }
}

contract FailingDelegator is DelegateScriptTarget {
function exec() public { revert(); }
function exec() public returns (bool) { revert(); }
}


contract DyingDelegator is DelegateScriptTarget {
function exec() public returns (bool) { selfdestruct(0); }
}


Expand All @@ -23,14 +28,13 @@ contract FailingDeployment {


contract ProtectionModifierKernel is ExecutorStorage, DelegateScriptTarget {
function exec() public {
function exec() public returns (bool) {
kernel = IKernel(0x1234);
}
}


contract ProtectionModifierAppId is ExecutorStorage, DelegateScriptTarget {
function exec() public {
function exec() public returns (bool) {
appId = bytes32(123456);
}
}
12 changes: 12 additions & 0 deletions test/mocks/MockDyingScriptExecutor.sol
@@ -0,0 +1,12 @@
pragma solidity 0.4.18;

import "../../contracts/evmscript/IEVMScriptExecutor.sol";


contract MockDyingScriptExecutor is IEVMScriptExecutor {
function execScript(bytes script, bytes input, address[] blacklist) external returns (bytes) {
if (input.length > 0) selfdestruct(address(0));

return new bytes(1);
}
}

0 comments on commit b4b45b8

Please sign in to comment.