Arithmetic fuzzer in a day

This commit is contained in:
Bhargava Shastry 2020-05-01 09:44:23 +02:00
parent 602b29cba7
commit f92f2ed78a
5 changed files with 727 additions and 1 deletions

View File

@ -10,7 +10,7 @@ add_dependencies(ossfuzz
if (OSSFUZZ)
add_custom_target(ossfuzz_proto)
add_dependencies(ossfuzz_proto yul_proto_ossfuzz yul_proto_diff_ossfuzz)
add_dependencies(ossfuzz_proto yul_proto_ossfuzz yul_proto_diff_ossfuzz sol_arith_proto_ossfuzz)
add_custom_target(ossfuzz_abiv2)
add_dependencies(ossfuzz_abiv2 abiv2_proto_ossfuzz)
@ -78,6 +78,48 @@ if (OSSFUZZ)
protobuf.a
)
set_target_properties(abiv2_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
add_executable(sol_arith_proto_ossfuzz
solArithProtoFuzzer.cpp
solarithprotoToSol.cpp
solArith.pb.cc
abiV2FuzzerCommon.cpp
../../EVMHost.cpp
)
target_include_directories(sol_arith_proto_ossfuzz PRIVATE
/usr/include/libprotobuf-mutator
)
target_link_libraries(sol_arith_proto_ossfuzz PRIVATE solidity libsolc
evmc
evmone-standalone
protobuf-mutator-libfuzzer.a
protobuf-mutator.a
protobuf.a
)
set_target_properties(sol_arith_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
# add_executable(sol_proto_ossfuzz
# solProtoFuzzer.cpp
# protoToSol.cpp
# solProto.pb.cc
# abiV2FuzzerCommon.cpp
# ../../EVMHost.cpp
# SolProtoAdaptor.cpp
# )
# target_include_directories(sol_proto_ossfuzz PRIVATE
# /usr/include/libprotobuf-mutator
# /home/bhargava/work/github/solidity/deps/LPM/external.protobuf/include
# /home/bhargava/work/github/solidity/deps/evmone/include
# /home/bhargava/work/github/solidity/deps/libprotobuf-mutator
# )
# target_link_libraries(sol_proto_ossfuzz PRIVATE solidity libsolc
# evmc
# evmone-standalone
# protobuf-mutator-libfuzzer.a
# protobuf-mutator.a
# protobuf.a
# )
# set_target_properties(sol_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
else()
add_library(solc_opt_ossfuzz
solc_opt_ossfuzz.cpp

View File

@ -0,0 +1,116 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
syntax = "proto2";
message Type {
enum Sign {
SIGNED = 0;
UNSIGNED = 1;
}
required Sign s = 1;
required uint32 bytewidth = 2;
}
message UnaryOpStmt {
enum Op {
POSTINC = 1;
POSTDEC = 2;
PREINC = 3;
PREDEC = 4;
}
required Op op = 1;
required VarRef v = 2;
}
message BinaryOpStmt {
enum Op {
ADDSELF = 0;
SUBSELF = 1;
MULSELF = 2;
DIVSELF = 3;
MODSELF = 4;
SHLSELF = 5;
SHRSELF = 6;
}
required Op op = 1;
required VarRef left = 2;
required Expression right = 3;
}
message BinaryOp {
enum Op {
ADD = 0;
SUB = 1;
MUL = 2;
DIV = 3;
MOD = 4;
EXP = 10;
SHL = 11;
SHR = 12;
}
required Op op = 1;
required Expression left = 2;
required Expression right = 3;
}
message VarDecl {
required Type t = 1;
required Expression value = 2;
}
message Assignment {
required VarRef id = 1;
required Expression value = 2;
}
message VarRef {
required uint32 id = 1;
}
message Expression {
oneof expr_oneof {
VarRef v = 1;
BinaryOp bop = 2;
}
}
message Return {
required Expression e = 1;
}
message Statement {
oneof stmt_oneof {
VarDecl vd = 1;
Assignment a = 2;
UnaryOpStmt u = 3;
}
}
message Block {
required VarDecl v = 1;
repeated Statement s = 2;
required Return r = 3;
}
message Program {
required Block b = 1;
required Type.Sign s = 2;
required uint64 seed = 3;
}
package solidity.test.solarithfuzzer;

View File

@ -0,0 +1,255 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <test/tools/ossfuzz/solarithprotoToSol.h>
#include <test/tools/ossfuzz/solArith.pb.h>
#include <test/tools/ossfuzz/abiV2FuzzerCommon.h>
#include <test/EVMHost.h>
#include <evmone/evmone.h>
#include <src/libfuzzer/libfuzzer_macro.h>
#include <fstream>
static evmc::VM evmone = evmc::VM{evmc_create_evmone()};
using namespace solidity::test::abiv2fuzzer;
using namespace solidity::test::solarithfuzzer;
using namespace solidity;
using namespace solidity::test;
using namespace solidity::util;
using namespace std;
namespace
{
/// Test function returns a uint256 value
//static size_t const expectedOutputLength = 32;
///// Expected output value is decimal 0
//static uint8_t const expectedOutput[expectedOutputLength] = {
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
//};
/// Compares the contents of the memory address pointed to
/// by `_result` of `_length` bytes to the expected output.
/// Returns true if `_result` matches expected output, false
/// otherwise.
//bool isOutputExpected(evmc::result const& _run)
//{
// if (_run.output_size != expectedOutputLength)
// return false;
//
// return (memcmp(_run.output_data, expectedOutput, expectedOutputLength) == 0);
//}
bool compareRuns(evmc::result const& _run1, evmc::result const& _run2)
{
if (_run1.output_size != _run2.output_size)
return false;
return memcmp(_run1.output_data, _run2.output_data, _run1.output_size) == 0;
}
/// Accepts a reference to a user-specified input and returns an
/// evmc_message with all of its fields zero initialized except
/// gas and input fields.
/// The gas field is set to the maximum permissible value so that we
/// don't run into out of gas errors. The input field is copied from
/// user input.
evmc_message initializeMessage(bytes const& _input)
{
// Zero initialize all message fields
evmc_message msg = {};
// Gas available (value of type int64_t) is set to its maximum
// value.
msg.gas = std::numeric_limits<int64_t>::max();
msg.input_data = _input.data();
msg.input_size = _input.size();
return msg;
}
/// Accepts host context implementation, and keccak256 hash of the function
/// to be called at a specified address in the simulated blockchain as
/// input and returns the result of the execution of the called function.
evmc::result executeContract(
EVMHost& _hostContext,
bytes const& _functionHash,
evmc_address _deployedAddress
)
{
evmc_message message = initializeMessage(_functionHash);
message.destination = _deployedAddress;
message.kind = EVMC_CALL;
return _hostContext.call(message);
}
/// Accepts a reference to host context implementation and byte code
/// as input and deploys it on the simulated blockchain. Returns the
/// result of deployment.
evmc::result deployContract(EVMHost& _hostContext, bytes const& _code)
{
evmc_message message = initializeMessage(_code);
message.kind = EVMC_CREATE;
return _hostContext.call(message);
}
std::pair<bytes, Json::Value> compileContract(
std::string _sourceCode,
std::string _contractName,
std::map<std::string, solidity::util::h160> const& _libraryAddresses = {},
frontend::OptimiserSettings _optimization = frontend::OptimiserSettings::minimal()
)
{
try
{
// Compile contract generated by the proto fuzzer
SolidityCompilationFramework solCompilationFramework;
return std::make_pair(
solCompilationFramework.compileContract(_sourceCode, _contractName, _libraryAddresses, _optimization),
solCompilationFramework.getMethodIdentifiers()
);
}
// Ignore stack too deep errors during compilation
catch (evmasm::StackTooDeepException const&)
{
return std::make_pair(bytes{}, Json::Value(0));
}
}
evmc::result deployAndExecute(EVMHost& _hostContext, bytes _byteCode, std::string _hexEncodedInput)
{
// Deploy contract and signal failure if deploy failed
evmc::result createResult = deployContract(_hostContext, _byteCode);
solAssert(
createResult.status_code == EVMC_SUCCESS,
"Proto solc fuzzer: Contract creation failed"
);
// Execute test function and signal failure if EVM reverted or
// did not return expected output on successful execution.
evmc::result callResult = executeContract(
_hostContext,
fromHex(_hexEncodedInput),
createResult.create_address
);
// We don't care about EVM One failures other than EVMC_REVERT
solAssert(callResult.status_code != EVMC_REVERT, "Proto solc fuzzer: EVM One reverted");
return callResult;
}
evmc::result compileDeployAndExecute(
std::string _sourceCode,
std::string _contractName,
std::string _methodName,
frontend::OptimiserSettings _optimization,
std::string _libraryName = {}
)
{
bytes libraryBytecode;
Json::Value libIds;
// We target the default EVM which is the latest
langutil::EVMVersion version = {};
EVMHost hostContext(version, evmone);
std::map<std::string, solidity::util::h160> _libraryAddressMap;
// First deploy library
if (!_libraryName.empty())
{
tie(libraryBytecode, libIds) = compileContract(
_sourceCode,
_libraryName,
{},
_optimization
);
// Deploy contract and signal failure if deploy failed
evmc::result createResult = deployContract(hostContext, libraryBytecode);
solAssert(
createResult.status_code == EVMC_SUCCESS,
"Proto solc fuzzer: Library deployment failed"
);
_libraryAddressMap[_libraryName] = EVMHost::convertFromEVMC(createResult.create_address);
}
auto [bytecode, ids] = compileContract(
_sourceCode,
_contractName,
_libraryAddressMap,
_optimization
);
return deployAndExecute(
hostContext,
bytecode,
ids[_methodName].asString()
);
}
}
DEFINE_PROTO_FUZZER(Program const& _input)
{
ProtoConverter converter;
string sol_source = converter.programToString(_input);
if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH"))
{
// With libFuzzer binary run this to generate a YUL source file x.yul:
// PROTO_FUZZER_DUMP_PATH=x.yul ./a.out proto-input
ofstream of(dump_path);
of.write(sol_source.data(), sol_source.size());
}
if (const char* dump_path = getenv("SOL_DEBUG_FILE"))
{
sol_source.clear();
// With libFuzzer binary run this to generate a YUL source file x.yul:
// PROTO_FUZZER_LOAD_PATH=x.yul ./a.out proto-input
ifstream ifstr(dump_path);
sol_source = {
std::istreambuf_iterator<char>(ifstr),
std::istreambuf_iterator<char>()
};
std::cout << sol_source << std::endl;
}
auto minimalResult = compileDeployAndExecute(
sol_source,
":C",
"test()",
frontend::OptimiserSettings::minimal(),
""
);
bool successState = minimalResult.status_code == EVMC_SUCCESS;
auto optResult = compileDeployAndExecute(
sol_source,
":C",
"test()",
frontend::OptimiserSettings::standard(),
""
);
if (successState)
{
solAssert(
optResult.status_code == EVMC_SUCCESS,
"Sol arith fuzzer: Optimal code failed"
);
solAssert(
compareRuns(minimalResult, optResult),
"Sol arith fuzzer: Runs produced different result"
);
}
}

View File

@ -0,0 +1,199 @@
#include <test/tools/ossfuzz/solarithprotoToSol.h>
#include <liblangutil/Exceptions.h>
#include <libsolutil/Whiskers.h>
using namespace solidity::test::solarithfuzzer;
using namespace solidity;
using namespace solidity::util;
using namespace std;
string ProtoConverter::programToString(Program const& _program)
{
m_rand = make_unique<SolRandomNumGenerator>(
SolRandomNumGenerator(_program.seed())
);
return visit(_program);
}
string ProtoConverter::visit(Program const& _program)
{
Whiskers p(R"(pragma solidity >= 0.0.0;)");
Whiskers c(R"(contract C {)");
Whiskers t(R"(function test() public returns (<type>)<body>)");
m_returnType = signString(_program.s());
t("type", m_returnType);
t("body", visit(_program.b()));
return p.render()
+ '\n'
+ c.render()
+ '\n'
+ '\t'
+ t.render()
+ '\n'
+ '}';
}
string ProtoConverter::visit(Return const& _ret)
{
Whiskers r(R"(return <type>(<expr>);)");
r("expr", visit(_ret.e()));
r("type", m_returnType);
return "\t\t" + r.render() + '\n';
}
string ProtoConverter::visit(Block const& _block)
{
ostringstream blockStr;
blockStr << '\n'
<< '\t'
<< '{'
<< '\n';
blockStr << visit(_block.v());
for (auto const& s: _block.s())
blockStr << visit(s);
blockStr << visit(_block.r());
blockStr << '\n'
<< '\t'
<< '}';
return blockStr.str();
}
string ProtoConverter::visit(Statement const& _stmt)
{
switch (_stmt.stmt_oneof_case())
{
case Statement::kVd:
return visit(_stmt.vd());
case Statement::kA:
return visit(_stmt.a());
case Statement::kU:
return visit(_stmt.u());
case Statement::STMT_ONEOF_NOT_SET:
return "";
}
}
string ProtoConverter::visit(VarDecl const& _vardecl)
{
bool varExists = varAvailable();
Whiskers v(R"(<type> <varName> = <type>(<value>);)");
string type = visit(_vardecl.t());
string varName = newVarName();
m_varTypeMap.emplace(varName, type);
v("type", type);
v("varName", varName);
v("value", varExists ? visit(_vardecl.value()) : to_string((*m_rand)()));
incrementVarCounter();
return "\t\t" + v.render() + '\n';
}
string ProtoConverter::visit(Type const& _type)
{
return signString(_type.s()) + widthString(_type.bytewidth());
}
string ProtoConverter::visit(UnaryOpStmt const& _uop)
{
switch (_uop.op())
{
case UnaryOpStmt_Op_POSTINC:
return "\t\t" + visit(_uop.v()) + "++;\n";
case UnaryOpStmt_Op_POSTDEC:
return "\t\t" + visit(_uop.v()) + "--;\n";
case UnaryOpStmt_Op_PREINC:
return "\t\t++" + visit(_uop.v()) + ";\n";
case UnaryOpStmt_Op_PREDEC:
return "\t\t--" + visit(_uop.v()) + ";\n";
}
}
string ProtoConverter::visit(BinaryOp const& _bop)
{
string op{};
switch (_bop.op())
{
case BinaryOp_Op_ADD:
op = " + ";
break;
case BinaryOp_Op_SUB:
op = " - ";
break;
case BinaryOp_Op_MUL:
op = " * ";
break;
case BinaryOp_Op_DIV:
op = " / ";
break;
case BinaryOp_Op_MOD:
op = " % ";
break;
// case BinaryOp_Op_ADDSELF:
// op = " += ";
// break;
// case BinaryOp_Op_SUBSELF:
// op = " -= ";
// break;
// case BinaryOp_Op_MULSELF:
// op = " *= ";
// break;
// case BinaryOp_Op_DIVSELF:
// op = " /= ";
// break;
// case BinaryOp_Op_MODSELF:
// op = " %= ";
// break;
case BinaryOp_Op_EXP:
op = " ** ";
break;
case BinaryOp_Op_SHL:
op = " << ";
break;
case BinaryOp_Op_SHR:
op = " >> ";
break;
// case BinaryOp_Op_SHLSELF:
// op = " <<= ";
// break;
// case BinaryOp_Op_SHRSELF:
// op = " >>= ";
// break;
}
return visit(_bop.left()) + op + visit(_bop.right());
}
string ProtoConverter::visit(Expression const& _expr)
{
switch (_expr.expr_oneof_case())
{
case Expression::kV:
solAssert(varAvailable(), "Sol arith fuzzer: Varref unavaileble");
return visit(_expr.v());
case Expression::kBop:
return visit(_expr.bop());
case Expression::EXPR_ONEOF_NOT_SET:
return "v0";
}
}
string ProtoConverter::visit(VarRef const&)
{
return randomVarName();
}
string ProtoConverter::visit(Assignment const& _assignment)
{
if (varAvailable())
{
string varName = visit(_assignment.id());
solAssert(m_varTypeMap.count(varName), "Sol arith fuzzer: Invalid varname");
Whiskers a(R"(<varName> = <type>(<expr>);)");
a("varName", varName);
a("type", m_varTypeMap[varName]);
a("expr", visit(_assignment.value()));
return "\t\t" + a.render() + '\n';
}
else
return "";
}

View File

@ -0,0 +1,114 @@
#include <test/tools/ossfuzz/solArith.pb.h>
#include <random>
namespace solidity::test::solarithfuzzer
{
/// Random number generator that is seeded with a fuzzer
/// supplied unsigned integer.
struct SolRandomNumGenerator
{
using RandomEngine = std::mt19937_64;
explicit SolRandomNumGenerator(unsigned _seed): m_random(RandomEngine(_seed)) {}
/// @returns a pseudo random unsigned integer
unsigned operator()()
{
return m_random();
}
RandomEngine m_random;
};
enum class Sign
{
Signed,
Unsigned
};
struct SolVarRef
{
SolVarRef(VarRef const& _varref);
std::string str();
Sign m_sign;
};
struct SolExpression
{
SolExpression(Expression const& _e);
std::string str();
Sign m_sign;
};
struct SolBinop
{
SolBinop(SolExpression const& _left, SolExpression const& _right);
std::string str();
Sign m_sign;
};
class ProtoConverter
{
public:
ProtoConverter()
{
}
ProtoConverter(ProtoConverter const&) = delete;
ProtoConverter(ProtoConverter&&) = delete;
std::string programToString(Program const& _input);
private:
std::string visit(Type const& _type);
std::string visit(UnaryOpStmt const& _uop);
std::string visit(BinaryOp const& _bop);
std::string visit(VarDecl const& _decl);
std::string visit(Assignment const& _assignment);
std::string visit(Return const& _return);
std::string visit(VarRef const& _varref);
std::string visit(Expression const& _expr);
std::string visit(Statement const& _stmt);
std::string visit(Block const& _block);
std::string visit(Program const& _program);
std::string newVarName()
{
return "v" + std::to_string(m_varCounter);
}
void incrementVarCounter()
{
m_varCounter++;
}
std::string randomVarName()
{
return "v" + std::to_string((*m_rand)() % m_varCounter);
}
static std::string signString(Type::Sign _sign)
{
return _sign == Type::Sign::Type_Sign_SIGNED ? "uint" : "uint";
}
static std::string widthString(unsigned _width)
{
return std::to_string((_width % 32 + 1) * 8);
}
bool varAvailable()
{
return m_varCounter > 0;
}
Sign typeSign(Type const& _ts)
{
return _ts.s() == Type::SIGNED ? Sign::Signed : Sign::Unsigned;
}
unsigned m_varCounter = 0;
std::shared_ptr<SolRandomNumGenerator> m_rand;
std::map<std::string, std::string> m_varTypeMap;
std::string m_returnType;
};
}