Commit 6bad0b79 authored by ben-chain's avatar ben-chain Committed by GitHub

More rigorous reverts for unsafe bytecode (#369)

* basic functionality

* update smock, fix broken smod check

* address PR feedback

* fix final tests, lock plugin version

* linting
parent 246e8dc9
...@@ -963,10 +963,24 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { ...@@ -963,10 +963,24 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
// revert data as to retrieve execution metadata that would normally be reverted out of // revert data as to retrieve execution metadata that would normally be reverted out of
// existence. // existence.
(bool success, bytes memory returndata) = bool success;
_isCreate bytes memory returndata;
? _handleContractCreation(_gasLimit, _data, _contract)
: _contract.call{gas: _gasLimit}(_data); if (_isCreate) {
// safeCREATE() is a function which replicates a CREATE message, but uses return values
// Which match that of CALL (i.e. bool, bytes). This allows many security checks to be
// to be shared between untrusted call and create call frames.
(success, returndata) = address(this).call(
abi.encodeWithSelector(
this.safeCREATE.selector,
_gasLimit,
_data,
_contract
)
);
} else {
(success, returndata) = _contract.call{gas: _gasLimit}(_data);
}
// Switch back to the original message context now that we're out of the call. // Switch back to the original message context now that we're out of the call.
_switchMessageContext(_nextMessageContext, prevMessageContext); _switchMessageContext(_nextMessageContext, prevMessageContext);
...@@ -1034,83 +1048,69 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { ...@@ -1034,83 +1048,69 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
/** /**
* Handles the creation-specific safety measures required for OVM contract deployment. * Handles the creation-specific safety measures required for OVM contract deployment.
* This function sanitizes the return types for creation messages to match calls (bool, bytes). * This function sanitizes the return types for creation messages to match calls (bool, bytes),
* by being an external function which the EM can call, that mimics the success/fail case of the CREATE.
* This allows for consistent handling of both types of messages in _handleExternalMessage(). * This allows for consistent handling of both types of messages in _handleExternalMessage().
* * Having this step occur as a separate call frame also allows us to easily revert the
* contract deployment in the event that the code is unsafe.
*
* @param _gasLimit Amount of gas to be passed into this creation. * @param _gasLimit Amount of gas to be passed into this creation.
* @param _creationCode Code to pass into CREATE for deployment. * @param _creationCode Code to pass into CREATE for deployment.
* @param _address OVM address being deployed to. * @param _address OVM address being deployed to.
* @return Whether or not the call succeeded.
* @return If creation fails: revert data. Otherwise: empty.
*/ */
function _handleContractCreation( function safeCREATE(
uint _gasLimit, uint _gasLimit,
bytes memory _creationCode, bytes memory _creationCode,
address _address address _address
) )
internal external
returns(
bool,
bytes memory
)
{ {
// The only way this should callable is from within _createContract(),
// and it should DEFINITELY not be callable by a non-EM code contract.
if (msg.sender != address(this)) {
return;
}
// Check that there is not already code at this address. // Check that there is not already code at this address.
if (_hasEmptyAccount(_address) == false) { if (_hasEmptyAccount(_address) == false) {
// Note: in the EVM, this case burns all allotted gas. For improved // Note: in the EVM, this case burns all allotted gas. For improved
// developer experience, we do return the remaining ones. // developer experience, we do return the remaining gas.
return ( _revertWithFlag(
false, RevertFlag.CREATE_COLLISION,
_encodeRevertData( Lib_ErrorUtils.encodeRevertString("A contract has already been deployed to this address")
RevertFlag.CREATE_COLLISION,
Lib_ErrorUtils.encodeRevertString("A contract has already been deployed to this address")
)
); );
} }
// Check the creation bytecode against the OVM_SafetyChecker. // Check the creation bytecode against the OVM_SafetyChecker.
if (ovmSafetyChecker.isBytecodeSafe(_creationCode) == false) { if (ovmSafetyChecker.isBytecodeSafe(_creationCode) == false) {
return ( _revertWithFlag(
false, RevertFlag.UNSAFE_BYTECODE,
_encodeRevertData( Lib_ErrorUtils.encodeRevertString("Contract creation code contains unsafe opcodes. Did you use the right compiler or pass an unsafe constructor argument?")
RevertFlag.UNSAFE_BYTECODE,
Lib_ErrorUtils.encodeRevertString("Contract creation code contains unsafe opcodes. Did you use the right compiler or pass an unsafe constructor argument?")
)
); );
} }
// We always need to initialize the contract with the default account values. // We always need to initialize the contract with the default account values.
_initPendingAccount(_address); _initPendingAccount(_address);
// Actually execute the EVM create message, // Actually execute the EVM create message.
// NOTE: The inline assembly below means we can NOT make any evm calls between here and then.
address ethAddress = Lib_EthUtils.createContract(_creationCode); address ethAddress = Lib_EthUtils.createContract(_creationCode);
if (ethAddress == address(0)) { if (ethAddress == address(0)) {
// If the creation fails, the EVM lets us grab its revert data. This may contain a revert flag // If the creation fails, the EVM lets us grab its revert data. This may contain a revert flag
// to be used above in _handleExternalMessage. // to be used above in _handleExternalMessage, so we pass the revert data back up unmodified.
uint256 revertDataSize; assembly {
assembly { revertDataSize := returndatasize() } returndatacopy(0,0,returndatasize())
bytes memory revertdata = new bytes(revertDataSize); revert(0, returndatasize())
assembly {
returndatacopy(
add(revertdata, 0x20),
0,
revertDataSize
)
} }
// Return that the creation failed, and the data it reverted with.
return (false, revertdata);
} }
// Again simply checking that the deployed code is safe too. Contracts can generate // Again simply checking that the deployed code is safe too. Contracts can generate
// arbitrary deployment code, so there's no easy way to analyze this beforehand. // arbitrary deployment code, so there's no easy way to analyze this beforehand.
bytes memory deployedCode = Lib_EthUtils.getCode(ethAddress); bytes memory deployedCode = Lib_EthUtils.getCode(ethAddress);
if (ovmSafetyChecker.isBytecodeSafe(deployedCode) == false) { if (ovmSafetyChecker.isBytecodeSafe(deployedCode) == false) {
return ( _revertWithFlag(
false, RevertFlag.UNSAFE_BYTECODE,
_encodeRevertData( Lib_ErrorUtils.encodeRevertString("Constructor attempted to deploy unsafe bytecode.")
RevertFlag.UNSAFE_BYTECODE,
Lib_ErrorUtils.encodeRevertString("Constructor attempted to deploy unsafe bytecode.")
)
); );
} }
...@@ -1121,9 +1121,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { ...@@ -1121,9 +1121,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
ethAddress, ethAddress,
Lib_EthUtils.getCodeHash(ethAddress) Lib_EthUtils.getCodeHash(ethAddress)
); );
// Successful deployments will not give access to returndata, in both the EVM and the OVM.
return (true, hex'');
} }
/****************************************** /******************************************
......
...@@ -51,8 +51,8 @@ ...@@ -51,8 +51,8 @@
}, },
"devDependencies": { "devDependencies": {
"@eth-optimism/dev": "^1.1.1", "@eth-optimism/dev": "^1.1.1",
"@eth-optimism/plugins": "^1.0.0-alpha.2", "@eth-optimism/plugins": "1.0.0-alpha.2",
"@eth-optimism/smock": "1.0.0-alpha.3", "@eth-optimism/smock": "1.0.0-alpha.6",
"@ethersproject/abstract-signer": "^5.0.14", "@ethersproject/abstract-signer": "^5.0.14",
"@nomiclabs/hardhat-ethers": "^2.0.1", "@nomiclabs/hardhat-ethers": "^2.0.1",
"@nomiclabs/hardhat-waffle": "^2.0.1", "@nomiclabs/hardhat-waffle": "^2.0.1",
......
...@@ -188,7 +188,7 @@ const test_nuisanceGas: TestDefinition = { ...@@ -188,7 +188,7 @@ const test_nuisanceGas: TestDefinition = {
// This is because there is natural gas consumption between the ovmCALL(GAS/2) and ovmCREATE, which allots nuisance gas via _getNuisanceGasLimit. // This is because there is natural gas consumption between the ovmCALL(GAS/2) and ovmCREATE, which allots nuisance gas via _getNuisanceGasLimit.
// This means that the ovmCREATE exception, DOES consumes all nuisance gas allotted, but that allotment // This means that the ovmCREATE exception, DOES consumes all nuisance gas allotted, but that allotment
// is less than the full OVM_TX_GAS_LIMIT / 2 which is alloted to the parent ovmCALL. // is less than the full OVM_TX_GAS_LIMIT / 2 which is alloted to the parent ovmCALL.
nuisanceGasLeft: 4531286, nuisanceGasLeft: 4197042,
}, },
}, },
}, },
......
...@@ -212,7 +212,7 @@ describe('OVM_BondManager', () => { ...@@ -212,7 +212,7 @@ describe('OVM_BondManager', () => {
// smodify the canClaim value to true to test claiming // smodify the canClaim value to true to test claiming
const block = await provider.getBlock('latest') const block = await provider.getBlock('latest')
timestamp = block.timestamp timestamp = block.timestamp
bondManager.smodify.set({ await bondManager.smodify.put({
witnessProviders: { witnessProviders: {
[preStateRoot]: { [preStateRoot]: {
canClaim: true, canClaim: true,
......
...@@ -312,7 +312,7 @@ describe('OVM_StateTransitioner', () => { ...@@ -312,7 +312,7 @@ describe('OVM_StateTransitioner', () => {
) )
) )
OVM_StateTransitioner.smodify.set({ await OVM_StateTransitioner.smodify.put({
phase: 0, phase: 0,
transactionHash, transactionHash,
}) })
...@@ -329,7 +329,7 @@ describe('OVM_StateTransitioner', () => { ...@@ -329,7 +329,7 @@ describe('OVM_StateTransitioner', () => {
describe('commitContractState', () => { describe('commitContractState', () => {
beforeEach(async () => { beforeEach(async () => {
OVM_StateTransitioner.smodify.set({ await OVM_StateTransitioner.smodify.put({
phase: 1, phase: 1,
}) })
}) })
...@@ -394,7 +394,7 @@ describe('OVM_StateTransitioner', () => { ...@@ -394,7 +394,7 @@ describe('OVM_StateTransitioner', () => {
proof = test.accountTrieWitness proof = test.accountTrieWitness
postStateRoot = test.newAccountTrieRoot postStateRoot = test.newAccountTrieRoot
OVM_StateTransitioner.smodify.put({ await OVM_StateTransitioner.smodify.put({
postStateRoot: test.accountTrieRoot, postStateRoot: test.accountTrieRoot,
}) })
}) })
...@@ -413,8 +413,8 @@ describe('OVM_StateTransitioner', () => { ...@@ -413,8 +413,8 @@ describe('OVM_StateTransitioner', () => {
}) })
describe('commitStorageSlot', () => { describe('commitStorageSlot', () => {
beforeEach(() => { beforeEach(async () => {
OVM_StateTransitioner.smodify.set({ await OVM_StateTransitioner.smodify.put({
phase: 1, phase: 1,
}) })
}) })
...@@ -510,7 +510,7 @@ describe('OVM_StateTransitioner', () => { ...@@ -510,7 +510,7 @@ describe('OVM_StateTransitioner', () => {
storageTrieProof = storageTest.proof storageTrieProof = storageTest.proof
OVM_StateTransitioner.smodify.put({ await OVM_StateTransitioner.smodify.put({
postStateRoot: test.accountTrieRoot, postStateRoot: test.accountTrieRoot,
}) })
}) })
...@@ -529,8 +529,8 @@ describe('OVM_StateTransitioner', () => { ...@@ -529,8 +529,8 @@ describe('OVM_StateTransitioner', () => {
}) })
describe('completeTransition', () => { describe('completeTransition', () => {
beforeEach(() => { beforeEach(async () => {
OVM_StateTransitioner.smodify.set({ await OVM_StateTransitioner.smodify.put({
phase: 1, phase: 1,
}) })
}) })
......
...@@ -4,6 +4,9 @@ import { defaultAccounts } from 'ethereum-waffle' ...@@ -4,6 +4,9 @@ import { defaultAccounts } from 'ethereum-waffle'
import { fromHexString, toHexString } from '@eth-optimism/core-utils' import { fromHexString, toHexString } from '@eth-optimism/core-utils'
import xor from 'buffer-xor' import xor from 'buffer-xor'
/* Internal Imports */
import { getContractDefinition } from '../../src'
export const DEFAULT_ACCOUNTS = defaultAccounts export const DEFAULT_ACCOUNTS = defaultAccounts
export const DEFAULT_ACCOUNTS_HARDHAT = defaultAccounts.map((account) => { export const DEFAULT_ACCOUNTS_HARDHAT = defaultAccounts.map((account) => {
return { return {
...@@ -35,8 +38,17 @@ export const NUISANCE_GAS_COSTS = { ...@@ -35,8 +38,17 @@ export const NUISANCE_GAS_COSTS = {
MIN_GAS_FOR_INVALID_STATE_ACCESS: 30000, MIN_GAS_FOR_INVALID_STATE_ACCESS: 30000,
} }
// TODO: get this exported/imported somehow in a way that we can do math on it. unfortunately using require('.....artifacts/contract.json') is erroring... let len
export const Helper_TestRunner_BYTELEN = 3654 // This is hacky, but `hardhat compile` evaluates this file for some reason.
// Feels better to have something hacky then a constant we have to keep re-hardcoding.
try {
len = fromHexString(
getContractDefinition('Helper_TestRunner').deployedBytecode
).byteLength
/* tslint:disable:no-empty */
} catch {}
export const Helper_TestRunner_BYTELEN = len
export const STORAGE_XOR = export const STORAGE_XOR =
'0xfeedfacecafebeeffeedfacecafebeeffeedfacecafebeeffeedfacecafebeef' '0xfeedfacecafebeeffeedfacecafebeeffeedfacecafebeeffeedfacecafebeef'
......
...@@ -121,8 +121,8 @@ export class ExecutionManagerTestRunner { ...@@ -121,8 +121,8 @@ export class ExecutionManagerTestRunner {
replacedParameter = this.setPlaceholderStrings(parameter) replacedParameter = this.setPlaceholderStrings(parameter)
}) })
beforeEach(() => { beforeEach(async () => {
this.contracts.OVM_StateManager.smodify.set({ await this.contracts.OVM_StateManager.smodify.put({
accounts: { accounts: {
[this.contracts.Helper_TestRunner.address]: { [this.contracts.Helper_TestRunner.address]: {
nonce: 0, nonce: 0,
...@@ -133,11 +133,11 @@ export class ExecutionManagerTestRunner { ...@@ -133,11 +133,11 @@ export class ExecutionManagerTestRunner {
}) })
}) })
beforeEach(() => { beforeEach(async () => {
this.contracts.OVM_ExecutionManager.smodify.set( await this.contracts.OVM_ExecutionManager.smodify.put(
replacedTest.preState.ExecutionManager replacedTest.preState.ExecutionManager
) )
this.contracts.OVM_StateManager.smodify.set( await this.contracts.OVM_StateManager.smodify.put(
replacedTest.preState.StateManager replacedTest.preState.StateManager
) )
}) })
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment