Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Require minimum return data from EVMScript executors to prevent selfdestructs #255

Merged
merged 2 commits into from Mar 27, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
}
}
}
7 changes: 6 additions & 1 deletion contracts/evmscript/executors/CallsScript.sol
Expand Up @@ -44,5 +44,10 @@ contract CallsScript is IEVMScriptExecutor {

location += (0x14 + 0x04 + calldataLength);
}

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;
}
}
}
2 changes: 1 addition & 1 deletion contracts/evmscript/executors/DeployDelegateScript.sol
Expand Up @@ -42,4 +42,4 @@ contract DeployDelegateScript is DelegateScript {
case 1 { revert(0, 0) } // throw if contract failed to deploy
}
}
}
}
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 @@ -279,5 +290,29 @@ 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')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this could check that the result is actually bytes(1)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Executor.execute doesn't return anything, it would require some changes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Executor.execute doesn't return anything, it would require some changes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah damn, ok. Let's not worry about it then.

})

it('fails when executor selfdestructs', async () => {
return assertRevert(async () => {
// passing some input makes executor to selfdestruct
await executor.executeWithIO('0x00000004', '0x0001', [])
})
})
})
})
})
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);
}
}