Value generator fuzzer

This commit is contained in:
Bhargava Shastry 2021-03-19 14:59:38 +01:00
parent c8e31e3466
commit 08f4a98ea6
4 changed files with 536 additions and 17 deletions

View File

@ -32,7 +32,7 @@ if (OSSFUZZ)
SolidityEvmoneInterface.cpp
../../libsolidity/util/ContractABIUtils.cpp
)
target_link_libraries(solc_ossfuzz PRIVATE libsolc evmasm evmc evmone-standalone)
target_link_libraries(solc_ossfuzz PRIVATE libsolc evmasm evmc evmone-standalone abicoder gmp.a)
set_target_properties(solc_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
add_executable(solc_mutator_ossfuzz

View File

@ -0,0 +1,326 @@
/*
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/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <test/tools/ossfuzz/ValueGenerator.h>
#include <liblangutil/Exceptions.h>
#include <regex>
#include <iostream>
using namespace std;
void ValueGenerator::initialiseType(TypeInfo& _t)
{
switch (_t.type)
{
case Type::Boolean:
_t.value += "true";
break;
case Type::Integer:
_t.value += "12";
break;
case Type::UInteger:
_t.value += "23";
break;
case Type::String:
_t.value += "0xdeadbeef";
break;
case Type::Bytes:
_t.value += "0xc0de";
break;
case Type::FixedBytes:
_t.value += "0x" + std::string(static_cast<size_t>(_t.fixedByteWidth) * 2, 'a');
break;
case Type::Address:
_t.value += "0x" + std::string(static_cast<size_t>(FixedBytesWidth::Bytes20) * 2, 'a');
break;
case Type::Function:
_t.value += "0x" + std::string(static_cast<size_t>(FixedBytesWidth::Bytes24) * 2, 'a');
break;
default:
solAssert(false, "Value Generator: Invalid value type.");
}
}
void ValueGenerator::initialiseTuple(TypeInfo& _tuple)
{
_tuple.value += "(";
std::string separator;
for (auto& c: _tuple.tupleInfo)
{
_tuple.value += separator + c.value;
// if (c.arrayInfo.empty())
// {
// if (c.type == Type::Tuple)
// initialiseTuple(c);
// else
// initialiseType(c);
// }
// else
// {
// initialiseArray(c.arrayInfo, c);
// cout << c.arrayInfo.size() << endl;
// cout << c.arrayInfo.back().numElements << endl;
// cout << c.value << endl;
// }
if (separator.empty())
separator = ",";
}
_tuple.value += ")";
}
void ValueGenerator::initialiseArray(
ArrayInfo& _arrayInfo,
TypeInfo& _typeInfo
)
{
cout << "Init 1D array" << endl;
cout << _typeInfo.value << endl;
_typeInfo.value += "[";
std::string separator;
for (size_t j = 0; j < _arrayInfo.numElements; j++)
{
_typeInfo.value += separator;
if (_typeInfo.type == Type::Tuple) {
cout << "Tuple inside array" << endl;
initialiseTuple(_typeInfo);
}
else
initialiseType(_typeInfo);
if (separator.empty())
separator = ",";
}
_typeInfo.value += "]";
cout << _typeInfo.value << endl;
}
void ValueGenerator::initialiseArray(
vector<ArrayInfo>& _arrayInfo,
TypeInfo& _typeInfo
)
{
if (_arrayInfo.size() == 1)
initialiseArray(_arrayInfo[0], _typeInfo);
else
{
vector<ArrayInfo> copy = _arrayInfo;
auto k = copy.back();
copy.pop_back();
_typeInfo.value += "[";
std::string separator;
for (size_t i = 0; i < k.numElements; i++)
{
_typeInfo.value += separator;
initialiseArray(copy, _typeInfo);
if (separator.empty())
separator = ",";
}
_typeInfo.value += "]";
}
}
void ValueGenerator::initialiseArrayOfTuple(
vector<ArrayInfo>&,
TypeInfo&
)
{
}
void ValueGenerator::typeHelper(Json::Value const& _type, TypeInfo& _typeInfo)
{
std::string jsonTypeString = _type["type"].asString();
/*
* Index | Match description
* 0 | Entire type string e.g., bool[1][2][]
* 1 | Type string e.g., bool, uint256, address etc.
* 2 | Base type e.g., uint, int, bytes
* 3 | Type width e.g., 256, 64, 1...32
* 4 | First array bracket e.g., [1] in uint256[1][2][3]
* 5 | First array dimension e.g., 1 in uint256[1][2][3]
* 6 | Second array bracket e.g., [2] in uint256[1][2][3]
* 7 | Second array dimension e.g., 2 in uint256[1][2][3]
* 8 | Third array bracket e.g., [3] in uint256[1][2][3]
* 9 | Third array dimension e.g., 3 in uint256[1][2][3]
*/
regex r = regex(
"(bool|(uint|int|bytes)(\\d+)|address|bytes|string|function|tuple)"
"(\\[(\\d+)?\\])?(\\[(\\d+)?\\])?(\\[(\\d+)?\\])?"
);
smatch matches;
auto match = regex_search(jsonTypeString, matches, r);
solAssert(match, "Value generator: Regex match failed.");
solAssert(
!matches[1].str().empty(),
"Value generator: Invalid type"
);
auto typeString = matches[1].str();
size_t width = 0;
if (matches[3].matched)
width = stoul(matches[3].str());
if (typeString.find("bool") != string::npos)
{
_typeInfo.type = Type::Boolean;
_typeInfo.name = "bool";
}
else if (typeString.find("function") != string::npos)
{
_typeInfo.type = Type::Function;
_typeInfo.name = "function";
}
else if (typeString.find("address") != string::npos)
{
_typeInfo.type = Type::Address;
_typeInfo.name = "address";
}
else if (typeString.find("tuple") != string::npos)
{
_typeInfo.type = Type::Tuple;
tuple(_type["components"], _typeInfo);
}
else if (typeString.find("bytes") != string::npos)
{
if (matches[3].matched)
{
solAssert(
width >= 1 && width <= 32,
"Value generator: Invalid fixed bytes type."
);
_typeInfo.type = Type::FixedBytes;
_typeInfo.fixedByteWidth = static_cast<FixedBytesWidth>(width);
_typeInfo.name = "bytes" + matches[3].str();
}
else
{
solAssert(width == 0, "Value generator: Invalid width.");
_typeInfo.type = Type::Bytes;
_typeInfo.name = "bytes";
}
}
else if (typeString.find("string") != string::npos)
{
_typeInfo.type = Type::String;
_typeInfo.name = "string";
}
else
{
std::string baseType = matches[2].str();
solAssert(
baseType == "int" || baseType == "uint",
"Value generator: Invalid integer type."
);
solAssert(
width >=8 && width <= 256 && (width % 8 == 0),
"Value generator: Invalid integer width."
);
if (baseType == "int")
{
_typeInfo.type = Type::Integer;
_typeInfo.intType = {true, static_cast<IntegerWidth>(width)};
_typeInfo.name = "int" + matches[3].str();
}
else
{
_typeInfo.type = Type::UInteger;
_typeInfo.intType = {false, static_cast<IntegerWidth>(width)};
_typeInfo.name = "uint" + matches[3].str();
}
}
for (unsigned i = 4; i < 10; i += 2)
{
if (matches[i].matched)
{
size_t arraySize;
if (matches[i + 1].matched)
{
std::string arraySizeString = matches[i + 1].str();
arraySize = stoul(arraySizeString);
_typeInfo.arrayInfo.push_back({true, arraySize});
_typeInfo.name += "[" + arraySizeString + "]";
}
else
{
// TODO: Assign pseudo randomly chosen dynamic size.
arraySize = 2;
_typeInfo.arrayInfo.push_back({false, arraySize});
_typeInfo.name += "[]";
}
}
}
if (_typeInfo.arrayInfo.empty())
{
if (_typeInfo.type == Type::Tuple) {
cout << "Init tuple" << endl;
initialiseTuple(_typeInfo);
}
else
initialiseType(_typeInfo);
}
else
{
cout << "Init array" << endl;
initialiseArray(_typeInfo.arrayInfo, _typeInfo);
}
}
void ValueGenerator::tuple(Json::Value const& _type, TypeInfo& _typeInfo)
{
_typeInfo.name += "(";
for (auto component = _type.begin(); component != _type.end();)
{
TypeInfo componentType;
typeHelper(*component, componentType);
_typeInfo.tupleInfo.emplace_back(componentType);
_typeInfo.name += _typeInfo.tupleInfo.back().name;
if (++component != _type.end())
{
_typeInfo.name += ",";
}
}
_typeInfo.name += ")";
}
ValueGenerator::TypeInfo ValueGenerator::type(Json::Value const& _value)
{
solAssert(
_value.isMember("type"),
"Value generator: Invalid function input parameter type."
);
TypeInfo result;
typeHelper(_value, result);
return result;
}
std::pair<std::string, std::string> ValueGenerator::type()
{
std::string typeString = "(";
std::string valueString = "(";
std::string separator = "";
for (auto const& param: m_type)
{
auto l = type(param);
typeString += separator + l.name;
valueString += separator + l.value;
if (separator.empty())
separator = ",";
}
return {typeString + ")", valueString + ")"};
}

View File

@ -0,0 +1,185 @@
/*
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/>.
*/
// SPDX-License-Identifier: GPL-3.0
#pragma once
#include <json/json.h>
#include <functional>
#include <map>
#include <optional>
#include <random>
#include <sstream>
#include <string>
class ValueGenerator
{
public:
enum class Type: size_t
{
Boolean = 0,
Integer,
UInteger,
FixedBytes,
Bytes,
String,
Address,
Function,
Tuple
};
struct ArrayInfo
{
bool staticSize;
size_t numElements;
};
enum class FixedBytesWidth: size_t
{
Bytes1 = 1,
Bytes2,
Bytes3,
Bytes4,
Bytes5,
Bytes6,
Bytes7,
Bytes8,
Bytes9,
Bytes10,
Bytes11,
Bytes12,
Bytes13,
Bytes14,
Bytes15,
Bytes16,
Bytes17,
Bytes18,
Bytes19,
Bytes20,
Bytes21,
Bytes22,
Bytes23,
Bytes24,
Bytes25,
Bytes26,
Bytes27,
Bytes28,
Bytes29,
Bytes30,
Bytes31,
Bytes32
};
enum class IntegerWidth: size_t
{
W8 = 8,
W16 = 16,
W24 = 24,
W32 = 32,
W40 = 40,
W48 = 48,
W56 = 56,
W64 = 64,
W72 = 72,
W80 = 80,
W88 = 88,
W96 = 96,
W104 = 104,
W112 = 112,
W120 = 120,
W128 = 128,
W136 = 136,
W144 = 144,
W152 = 152,
W160 = 160,
W168 = 168,
W176 = 176,
W184 = 184,
W192 = 192,
W200 = 200,
W208 = 208,
W216 = 216,
W224 = 224,
W232 = 232,
W240 = 240,
W248 = 248,
W256 = 256
};
struct IntegerType
{
bool sign;
IntegerWidth width;
};
struct TypeInfo
{
Type type;
FixedBytesWidth fixedByteWidth;
IntegerType intType;
std::vector<ArrayInfo> arrayInfo;
std::vector<TypeInfo> tupleInfo;
std::string name;
std::string value;
};
explicit ValueGenerator(Json::Value const& _type, unsigned _seed):
m_rand(_seed),
m_type(_type),
m_bernoulli(0.5)
{}
void boolean()
{
m_stream << (m_bernoulli(m_rand) ? "true" : "false");
}
void string()
{
m_stream << "hello";
}
void bytes()
{
m_stream << "0x1234";
}
void function();
void fixedbytes()
{
}
void address();
void integer()
{
}
static void typeHelper(Json::Value const& _type, TypeInfo& _typeInfo);
static TypeInfo type(Json::Value const& _type);
static void tuple(Json::Value const& _tuple, TypeInfo& _typeInfo);
std::pair<std::string, std::string> type();
static void initialiseTuple(TypeInfo& _tuple);
static void initialiseType(TypeInfo& _t);
static void initialiseArray(
ArrayInfo& _arrayInfo,
TypeInfo& _typeInfo
);
static void initialiseArrayOfTuple(
std::vector<ArrayInfo>& _arrayInfo,
TypeInfo& _typeInfo
);
static void initialiseArray(
std::vector<ArrayInfo>& _arrayInfo,
TypeInfo& _typeInfo
);
private:
std::ostringstream m_stream;
std::minstd_rand m_rand;
Json::Value const& m_type;
std::bernoulli_distribution m_bernoulli;
};

View File

@ -32,6 +32,8 @@
#include <evmone/evmone.h>
#include <abicoder.hpp>
#include <sstream>
using namespace solidity::frontend::test;
@ -45,10 +47,11 @@ using namespace std;
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size);
static evmc::VM evmone = evmc::VM{evmc_create_evmone()};
static constexpr size_t abiCoderHeapSize = 1024 * 512;
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size)
{
if (_size <= 600)
// if (_size <= 600)
{
string input(reinterpret_cast<char const*>(_data), _size);
// TODO: Cannot fuzz tests containing libraries yet.
@ -87,27 +90,33 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size)
if (!compilerOutput.has_value())
return 0;
optional<string> noInputFunction = evmoneUtil.noInputFunction();
if (auto r = evmoneUtil.randomFunction(); r.has_value())
auto r = evmoneUtil.randomFunction();
if (!r.has_value())
return 0;
auto x = ValueGenerator{r.value()["inputs"], 0}.type();
bool encodeStatus;
string encodedData;
bool functionWithInputs = x.first != "()";
if (functionWithInputs)
{
cout << ContractABIUtils{}.functionSignatureFromABI(r.value()) << endl;
for (auto const& i: r.value()["inputs"])
if (auto v = ValueGenerator{i["type"].asString(), 0}.type(); v.has_value())
cout << v.value().name << endl;
return 0;
abicoder::ABICoder coder(abiCoderHeapSize);
auto s = coder.encode(x.first, x.second);
encodeStatus = s.first;
encodedData = s.second;
solAssert(encodeStatus, "Isabelle coder: failed.");
}
if (noInputFunction.has_value())
evmoneUtil.methodName(noInputFunction.value());
else
return 0;
auto deployResult = evmoneUtil.deployContract(compilerOutput->byteCode);
if (deployResult.status_code != EVMC_SUCCESS)
return 0;
auto methodSig = solidity::util::fromHex(compilerOutput->methodIdentifiersInContract[noInputFunction.value()].asString());
auto sig = r.value()["name"].asString() + x.first;
auto methodSig = compilerOutput->methodIdentifiersInContract[sig].asString();
if (functionWithInputs)
methodSig += encodedData.substr(2, encodedData.size());
auto callResult = evmoneUtil.executeContract(
methodSig,
solidity::util::fromHex(methodSig),
deployResult.create_address
);
@ -137,7 +146,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size)
solAssert(deployResultOpt.status_code == EVMC_SUCCESS, "Contract compiled via new code gen could not be deployed.");
auto callResultOpt = evmoneUtil.executeContract(
methodSig,
solidity::util::fromHex(methodSig),
deployResultOpt.create_address
);
solAssert(callResultOpt.status_code == EVMC_SUCCESS, "New code gen contract call failed.");
@ -181,5 +190,4 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size)
return 0;
}
}
return 0;
}