Filter false positives due to EVM errors.

Co-authored-by: Daniel Kirchner <daniel@ekpyron.org>
This commit is contained in:
Bhargava Shastry 2021-03-08 14:33:20 +01:00
parent 34c08ea2c6
commit 24f42c5541
5 changed files with 76 additions and 19 deletions

View File

@ -773,7 +773,7 @@ evmc::result EVMHost::resultWithGas(
return result; return result;
} }
void EVMHost::print_storage_at(evmc::address const& _addr, ostringstream& _os) void EVMHost::printStorageAt(evmc::address const& _addr, ostringstream& _os)
{ {
for (auto const& [slot, value]: get_address_storage(_addr)) for (auto const& [slot, value]: get_address_storage(_addr))
if (get_storage(_addr, slot)) if (get_storage(_addr, slot))
@ -786,9 +786,9 @@ StorageMap const& EVMHost::get_address_storage(evmc::address const& _addr)
return accounts[_addr].storage; return accounts[_addr].storage;
} }
void EVMHost::print_call_records(std::ostringstream& _os) const noexcept void EVMHost::printCallRecords(std::ostringstream& _os) const noexcept
{ {
auto callKind = [](evmc_call_kind _kind) -> string static const auto callKind = [](evmc_call_kind _kind) -> string
{ {
switch (_kind) switch (_kind)
{ {
@ -814,7 +814,7 @@ void EVMHost::print_call_records(std::ostringstream& _os) const noexcept
<< endl; << endl;
} }
void EVMHost::print_selfdestruct_records(ostringstream& _os) const noexcept void EVMHost::printSelfdestructRecords(ostringstream& _os) const noexcept
{ {
for (auto const& record: recorded_selfdestructs) for (auto const& record: recorded_selfdestructs)
_os << "SELFDESTRUCT" _os << "SELFDESTRUCT"
@ -825,7 +825,7 @@ void EVMHost::print_selfdestruct_records(ostringstream& _os) const noexcept
<< endl; << endl;
} }
void EVMHost::print_balance(evmc::address const& _addr, ostringstream& _os) const noexcept void EVMHost::printBalance(evmc::address const& _addr, ostringstream& _os) const noexcept
{ {
_os << "BALANCE " << convertFromEVMC(get_balance(_addr)) << endl; _os << "BALANCE " << convertFromEVMC(get_balance(_addr)) << endl;
} }
@ -837,13 +837,13 @@ string EVMHost::dumpState(evmc::address _addr)
// Print state and execution trace. // Print state and execution trace.
if (account_exists(_addr)) if (account_exists(_addr))
{ {
print_storage_at(_addr, stateStream); printStorageAt(_addr, stateStream);
print_balance(_addr, stateStream); printBalance(_addr, stateStream);
} }
else else
print_selfdestruct_records(stateStream); printSelfdestructRecords(stateStream);
print_call_records(stateStream); printCallRecords(stateStream);
return stateStream.str(); return stateStream.str();
} }

View File

@ -97,13 +97,13 @@ private:
/// Records calls made via @param _message. /// Records calls made via @param _message.
void recordCalls(evmc_message const& _message) noexcept; void recordCalls(evmc_message const& _message) noexcept;
/// Prints contents of storage at @param _addr to @param _os. /// Prints contents of storage at @param _addr to @param _os.
void print_storage_at(evmc::address const& _addr, std::ostringstream& _os); void printStorageAt(evmc::address const& _addr, std::ostringstream& _os);
/// Prints call summary to @param _os. /// Prints call summary to @param _os.
void print_call_records(std::ostringstream& _os) const noexcept; void printCallRecords(std::ostringstream& _os) const noexcept;
/// Print self destruct records to @param _os. /// Print self destruct records to @param _os.
void print_selfdestruct_records(std::ostringstream& _os) const noexcept; void printSelfdestructRecords(std::ostringstream& _os) const noexcept;
/// Print balance of @param _addr to @param _os. /// Print balance of @param _addr to @param _os.
void print_balance(evmc::address const& _addr, std::ostringstream& _os) const noexcept; void printBalance(evmc::address const& _addr, std::ostringstream& _os) const noexcept;
static evmc::result precompileECRecover(evmc_message const& _message) noexcept; static evmc::result precompileECRecover(evmc_message const& _message) noexcept;
static evmc::result precompileSha256(evmc_message const& _message) noexcept; static evmc::result precompileSha256(evmc_message const& _message) noexcept;

View File

@ -49,6 +49,10 @@ static evmc::VM evmone = evmc::VM{evmc_create_evmone()};
DEFINE_PROTO_FUZZER(Program const& _input) DEFINE_PROTO_FUZZER(Program const& _input)
{ {
// Solidity creates an invalid instruction for subobjects, so we simply
// ignore them in this fuzzer.
if (_input.has_obj())
return;
bool filterStatefulInstructions = true; bool filterStatefulInstructions = true;
bool filterUnboundedLoops = true; bool filterUnboundedLoops = true;
ProtoConverter converter( ProtoConverter converter(
@ -95,17 +99,26 @@ DEFINE_PROTO_FUZZER(Program const& _input)
// If the fuzzer synthesized input does not contain the revert opcode which // If the fuzzer synthesized input does not contain the revert opcode which
// we lazily check by string find, the EVM call should not revert. // we lazily check by string find, the EVM call should not revert.
bool noRevertInSource = yul_source.find("revert") == string::npos; bool noRevertInSource = yul_source.find("revert") == string::npos;
bool noInvalidInSource = yul_source.find("invalid") == string::npos;
if (noInvalidInSource)
solAssert(
callResult.status_code != EVMC_INVALID_INSTRUCTION,
"Invalid instruction."
);
if (noRevertInSource) if (noRevertInSource)
solAssert( solAssert(
callResult.status_code != EVMC_REVERT, callResult.status_code != EVMC_REVERT,
"SolidityEvmoneInterface: EVM One reverted" "SolidityEvmoneInterface: EVM One reverted"
); );
// Out of gas errors are problematic because it is possible that the // Bail out on serious errors encountered during a call.
// optimizer makes them go away, making EVM state impossible to if (YulEvmoneUtility{}.seriousCallError(callResult.status_code))
// compare in general.
if (callResult.status_code == EVMC_OUT_OF_GAS)
return; return;
solAssert(
(callResult.status_code == EVMC_SUCCESS ||
(!noRevertInSource && callResult.status_code == EVMC_REVERT) ||
(!noInvalidInSource && callResult.status_code == EVMC_INVALID_INSTRUCTION)),
"Unoptimised call failed."
);
ostringstream unoptimizedState; ostringstream unoptimizedState;
unoptimizedState << hostContext.dumpState(deployResult.create_address); unoptimizedState << hostContext.dumpState(deployResult.create_address);
@ -135,10 +148,34 @@ DEFINE_PROTO_FUZZER(Program const& _input)
callResultOpt.status_code != EVMC_REVERT, callResultOpt.status_code != EVMC_REVERT,
"SolidityEvmoneInterface: EVM One reverted" "SolidityEvmoneInterface: EVM One reverted"
); );
if (noInvalidInSource)
solAssert(
callResultOpt.status_code != EVMC_INVALID_INSTRUCTION,
"Invalid instruction."
);
solAssert(
(callResultOpt.status_code == EVMC_SUCCESS ||
(!noRevertInSource && callResultOpt.status_code == EVMC_REVERT) ||
(!noInvalidInSource && callResultOpt.status_code == EVMC_INVALID_INSTRUCTION)),
"Optimised call failed."
);
ostringstream optimizedState; ostringstream optimizedState;
optimizedState << hostContext.dumpState(deployResultOpt.create_address); optimizedState << hostContext.dumpState(deployResultOpt.create_address);
int64_t constexpr tolerance = 1000;
if (callResult.gas_left > callResultOpt.gas_left)
if (callResult.gas_left - callResultOpt.gas_left > tolerance)
{
cout << "Gas differential " << callResult.gas_left - callResultOpt.gas_left << endl;
cout << "Unoptimised bytecode" << endl;
cout << util::toHex(unoptimisedByteCode) << endl;
cout << "Optimised bytecode" << endl;
cout << util::toHex(optimisedByteCode) << endl;
solAssert(false, "Optimised code consumed more than +1000 gas.");
}
solAssert( solAssert(
unoptimizedState.str() == optimizedState.str(), unoptimizedState.str() == optimizedState.str(),
"Storage of unoptimised and optimised stack reused code do not match." "State of unoptimised and optimised stack reused code do not match."
); );
} }

View File

@ -78,3 +78,21 @@ evmc_message YulEvmoneUtility::callMessage(evmc_address _address)
call.kind = EVMC_CALL; call.kind = EVMC_CALL;
return call; return call;
} }
bool YulEvmoneUtility::seriousCallError(evmc_status_code _code)
{
if (_code == EVMC_OUT_OF_GAS)
return true;
else if (_code == EVMC_STACK_OVERFLOW)
return true;
else if (_code == EVMC_STACK_UNDERFLOW)
return true;
else if (_code == EVMC_INTERNAL_ERROR)
return true;
else if (_code == EVMC_UNDEFINED_INSTRUCTION)
return true;
else if (_code == EVMC_INVALID_MEMORY_ACCESS)
return true;
else
return false;
}

View File

@ -54,5 +54,7 @@ struct YulEvmoneUtility
static evmc::result deployCode(solidity::bytes const& _input, EVMHost& _host); static evmc::result deployCode(solidity::bytes const& _input, EVMHost& _host);
/// @returns call message to be sent to @param _address. /// @returns call message to be sent to @param _address.
static evmc_message callMessage(evmc_address _address); static evmc_message callMessage(evmc_address _address);
/// @returns true if call result indicates a serious error, false otherwise.
static bool seriousCallError(evmc_status_code _code);
}; };
} }