diff --git a/test/tools/ossfuzz/CMakeLists.txt b/test/tools/ossfuzz/CMakeLists.txt index bed8762c2..399eada46 100644 --- a/test/tools/ossfuzz/CMakeLists.txt +++ b/test/tools/ossfuzz/CMakeLists.txt @@ -1,3 +1,4 @@ +link_directories(/src/LPM/src /src/LPM/src/libfuzzer /src/LPM/external.protobuf/lib) add_custom_target(ossfuzz) add_dependencies(ossfuzz solc_opt_ossfuzz @@ -6,6 +7,9 @@ add_dependencies(ossfuzz strictasm_diff_ossfuzz ) +add_custom_target(ossfuzz_proto) +add_dependencies(ossfuzz_proto yul_proto_ossfuzz yul_proto_diff_ossfuzz) + #[[FuzzingEngine.a is provided by oss-fuzz's Dockerized build environment]] add_executable(solc_opt_ossfuzz solc_opt_ossfuzz.cpp ../fuzzer_common.cpp) target_link_libraries(solc_opt_ossfuzz PRIVATE libsolc evmasm FuzzingEngine.a) @@ -17,4 +21,21 @@ add_executable(const_opt_ossfuzz const_opt_ossfuzz.cpp ../fuzzer_common.cpp) target_link_libraries(const_opt_ossfuzz PRIVATE libsolc evmasm FuzzingEngine.a) add_executable(strictasm_diff_ossfuzz strictasm_diff_ossfuzz.cpp yulFuzzerCommon.cpp) -target_link_libraries(strictasm_diff_ossfuzz PRIVATE libsolc evmasm yulInterpreter FuzzingEngine.a) \ No newline at end of file +target_link_libraries(strictasm_diff_ossfuzz PRIVATE libsolc evmasm yulInterpreter FuzzingEngine.a) + +add_executable(yul_proto_ossfuzz yulProtoFuzzer.cpp protoToYul.cpp yulProto.pb.cc) +target_include_directories(yul_proto_ossfuzz PRIVATE /src/libprotobuf-mutator /src/LPM/external.protobuf/include) +target_link_libraries(yul_proto_ossfuzz PRIVATE yul evmasm + protobuf-mutator-libfuzzer.a + protobuf-mutator.a + protobuf.a + FuzzingEngine.a) + +add_executable(yul_proto_diff_ossfuzz yulProto_diff_ossfuzz.cpp yulFuzzerCommon.cpp protoToYul.cpp yulProto.pb.cc) +target_include_directories(yul_proto_diff_ossfuzz PRIVATE /src/libprotobuf-mutator /src/LPM/external.protobuf/include) +target_link_libraries(yul_proto_diff_ossfuzz PRIVATE yul evmasm + yulInterpreter + protobuf-mutator-libfuzzer.a + protobuf-mutator.a + protobuf.a + FuzzingEngine.a) diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp new file mode 100644 index 000000000..c7df387e2 --- /dev/null +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -0,0 +1,242 @@ +/* + 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 . +*/ + +#include + +#include +#include + +using namespace std; +using namespace yul::test::yul_fuzzer; + +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Literal const& _x) +{ + return _os << "(" << _x.val() << ")"; +} + +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, VarRef const& _x) +{ + return _os << "x_" << (static_cast(_x.varnum()) % 10); +} + +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Expression const& _x) +{ + if (_x.has_varref()) + return _os << _x.varref(); + else if (_x.has_cons()) + return _os << _x.cons(); + else if (_x.has_binop()) + return _os << _x.binop(); + else if (_x.has_unop()) + return _os << _x.unop(); + return _os << ""; +} + +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, BinaryOp const& _x) +{ + switch (_x.op()) + { + case BinaryOp::ADD: + _os << "add("; + break; + case BinaryOp::SUB: + _os << "sub("; + break; + case BinaryOp::MUL: + _os << "mul("; + break; + case BinaryOp::DIV: + _os << "div("; + break; + case BinaryOp::MOD: + _os << "mod("; + break; + case BinaryOp::XOR: + _os << "xor("; + break; + case BinaryOp::AND: + _os << "and("; + break; + case BinaryOp::OR: + _os << "or("; + break; + case BinaryOp::EQ: + _os << "eq("; + break; + case BinaryOp::LT: + _os << "lt("; + break; + case BinaryOp::GT: + _os << "gt("; + break; + } + return _os << _x.left() << "," << _x.right() << ")"; +} + +// New var numbering starts from x_10 until x_16 +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, VarDecl const& _x) +{ + _os << "let x_" << ((_x.id() % 7) + 10) << " := " << _x.expr() << "\n"; + return _os; +} + +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, TypedVarDecl const& _x) +{ + _os << "let x_" << ((_x.id() % 7) + 10); + switch (_x.type()) + { + case TypedVarDecl::BOOL: + _os << ": bool := " << _x.expr() << " : bool\n"; + break; + case TypedVarDecl::S8: + _os << ": s8 := " << _x.expr() << " : s8\n"; + break; + case TypedVarDecl::S32: + _os << ": s32 := " << _x.expr() << " : s32\n"; + break; + case TypedVarDecl::S64: + _os << ": s64 := " << _x.expr() << " : s64\n"; + break; + case TypedVarDecl::S128: + _os << ": s128 := " << _x.expr() << " : s128\n"; + break; + case TypedVarDecl::S256: + _os << ": s256 := " << _x.expr() << " : s256\n"; + break; + case TypedVarDecl::U8: + _os << ": u8 := " << _x.expr() << " : u8\n"; + break; + case TypedVarDecl::U32: + _os << ": u32 := " << _x.expr() << " : u32\n"; + break; + case TypedVarDecl::U64: + _os << ": u64 := " << _x.expr() << " : u64\n"; + break; + case TypedVarDecl::U128: + _os << ": u128 := " << _x.expr() << " : u128\n"; + break; + case TypedVarDecl::U256: + _os << ": u256 := " << _x.expr() << " : u256\n"; + break; + } + return _os; +} + +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, UnaryOp const& _x) +{ + switch (_x.op()) + { + case UnaryOp::NOT: + _os << "not("; + break; + case UnaryOp::MLOAD: + _os << "mload("; + break; + case UnaryOp::SLOAD: + _os << "sload("; + break; + case UnaryOp::ISZERO: + _os << "iszero("; + break; + } + _os << _x.operand() << ")"; + return _os; +} + +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, AssignmentStatement const& _x) +{ + return _os << _x.ref_id() << " := " << _x.expr() << "\n"; +} + +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, IfStmt const& _x) +{ + return _os << + "if " << + _x.cond() << + " " << + _x.if_body(); +} + +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, StoreFunc const& _x) +{ + switch (_x.st()) + { + case StoreFunc::MSTORE: + _os << "mstore(" << _x.loc() << ", " << _x.val() << ")\n"; + break; + case StoreFunc::SSTORE: + _os << "sstore(" << _x.loc() << ", " << _x.val() << ")\n"; + break; + } + return _os; +} + +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Statement const& _x) +{ + if (_x.has_decl()) + return _os << _x.decl(); + else if (_x.has_assignment()) + return _os << _x.assignment(); + else if (_x.has_ifstmt()) + return _os << _x.ifstmt(); + else if (_x.has_storage_func()) + return _os << _x.storage_func(); + else if (_x.has_blockstmt()) + return _os << _x.blockstmt(); + return _os << ""; +} + +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Block const& _x) +{ + if (_x.statements_size() > 0) + { + _os << "{\n"; + for (auto const& st: _x.statements()) + _os << st; + _os << "}\n"; + + } + return _os; +} + +ostream& yul::test::yul_fuzzer::operator<<(ostream& _os, Function const& _x) +{ + _os << "{\n" + << "let a,b := foo(calldataload(0),calldataload(32),calldataload(64),calldataload(96),calldataload(128)," + << "calldataload(160),calldataload(192),calldataload(224))\n" + << "sstore(0, a)\n" + << "sstore(32, b)\n" + << "function foo(x_0, x_1, x_2, x_3, x_4, x_5, x_6, x_7) -> x_8, x_9\n" + << _x.statements() + << "}\n"; + return _os; +} + +string yul::test::yul_fuzzer::functionToString(Function const& _input) +{ + ostringstream os; + os << _input; + return os.str(); +} + +string yul::test::yul_fuzzer::protoToYul(const uint8_t* _data, size_t _size) +{ + Function message; + if (!message.ParsePartialFromArray(_data, _size)) + return "#error invalid proto\n"; + return functionToString(message); +} \ No newline at end of file diff --git a/test/tools/ossfuzz/protoToYul.h b/test/tools/ossfuzz/protoToYul.h new file mode 100644 index 000000000..14389c547 --- /dev/null +++ b/test/tools/ossfuzz/protoToYul.h @@ -0,0 +1,51 @@ +/* + 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 . +*/ + +#include +#include +#include + +#include + +namespace yul +{ +namespace test +{ +namespace yul_fuzzer +{ +class Function; + +std::string functionToString(Function const& input); +std::string protoToYul(uint8_t const* data, size_t size); +std::ostream& operator<<(std::ostream& _os, BinaryOp const& _x); +std::ostream& operator<<(std::ostream& _os, Block const& _x); +std::ostream& operator<<(std::ostream& _os, Literal const& _x); +std::ostream& operator<<(std::ostream& _os, VarRef const& _x); +std::ostream& operator<<(std::ostream& _os, Expression const& _x); +std::ostream& operator<<(std::ostream& _os, BinaryOp const& _x); +std::ostream& operator<<(std::ostream& _os, VarDecl const& _x); +std::ostream& operator<<(std::ostream& _os, TypedVarDecl const& _x); +std::ostream& operator<<(std::ostream& _os, UnaryOp const& _x); +std::ostream& operator<<(std::ostream& _os, AssignmentStatement const& _x); +std::ostream& operator<<(std::ostream& _os, IfStmt const& _x); +std::ostream& operator<<(std::ostream& _os, StoreFunc const& _x); +std::ostream& operator<<(std::ostream& _os, Statement const& _x); +std::ostream& operator<<(std::ostream& _os, Block const& _x); +std::ostream& operator<<(std::ostream& _os, Function const& _x); +} +} +} \ No newline at end of file diff --git a/test/tools/ossfuzz/yulProto.proto b/test/tools/ossfuzz/yulProto.proto new file mode 100644 index 000000000..470e2a36f --- /dev/null +++ b/test/tools/ossfuzz/yulProto.proto @@ -0,0 +1,156 @@ +/* + 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 . +*/ + +syntax = "proto2"; + +// VariableDeclaration = +// 'let' TypedIdentifierList ( ':=' Expression )? +// TypedIdentifierList = Identifier ':' TypeName ( ',' Identifier ':' TypeName )* +// Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]* +// IdentifierList = Identifier ( ',' Identifier)* +// TypeName = Identifier | BuiltinTypeName +// BuiltinTypeName = 'bool' | [us] ( '8' | '32' | '64' | '128' | '256' ) +message VarDecl { + required int32 id = 1; + required Expression expr = 3; +} + +message TypedVarDecl { + enum TypeName { + BOOL = 1; + U8 = 2; + U32 = 3; + U64 = 4; + U128 = 5; + U256 = 6; + S8 = 7; + S32 = 8; + S64 = 9; + S128 = 10; + S256 = 11; + }; + required int32 id = 1; + required TypeName type = 2; + required Expression expr = 3; +} + +message VarRef { + required int32 varnum = 1; +} + +message Literal { + required int32 val = 1; +} + +message TypedLiteral { + enum TypeName { + BOOL = 1; + U8 = 2; + U32 = 3; + U64 = 4; + U128 = 5; + U256 = 6; + S8 = 7; + S32 = 8; + S64 = 9; + S128 = 10; + S256 = 11; + }; + required int32 val = 1; + required TypeName type = 2; +} + +message BinaryOp { + enum BOp { + ADD = 0; + SUB = 1; + MUL = 2; + DIV = 3; + MOD = 4; + XOR = 5; + AND = 6; + OR = 7; + EQ = 8; + LT = 9; + GT = 10; + }; + required BOp op = 1; + required Expression left = 2; + required Expression right = 3; +} + +message UnaryOp { + enum UOp { + NOT = 0; + MLOAD = 1; + SLOAD = 2; + ISZERO = 3; + } + required UOp op = 1; + required Expression operand = 2; +} + +message StoreFunc { + enum Storage { + MSTORE = 0; + SSTORE = 1; + } + required Expression loc = 1; + required Expression val = 2; + required Storage st = 3; +} + +message Expression { + oneof expr_oneof { + VarRef varref = 1; + Literal cons = 2; + BinaryOp binop = 3; + UnaryOp unop = 4; + } +} + +message AssignmentStatement { + required VarRef ref_id = 1; + required Expression expr = 2; +} + +message IfStmt { + required Expression cond = 1; + required Block if_body = 2; +} + +// add for loop +// TODO: add block and scope for if +message Statement { + oneof stmt_oneof { + VarDecl decl = 1; + AssignmentStatement assignment = 2; + IfStmt ifstmt = 3; + StoreFunc storage_func = 4; + Block blockstmt = 5; + } +} + +message Block { + repeated Statement statements = 1; +} + +message Function { + required Block statements = 1; +} + +package yul.test.yul_fuzzer; diff --git a/test/tools/ossfuzz/yulProtoFuzzer.cpp b/test/tools/ossfuzz/yulProtoFuzzer.cpp new file mode 100644 index 000000000..b959bc1d8 --- /dev/null +++ b/test/tools/ossfuzz/yulProtoFuzzer.cpp @@ -0,0 +1,59 @@ +/* + 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 . +*/ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +using namespace yul; +using namespace yul::test::yul_fuzzer; +using namespace std; + +DEFINE_BINARY_PROTO_FUZZER(Function const& _input) +{ + string yul_source = functionToString(_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(yul_source.data(), yul_source.size()); + } + + // AssemblyStack entry point + AssemblyStack stack(langutil::EVMVersion(), AssemblyStack::Language::StrictAssembly); + + // Parse protobuf mutated YUL code + if (!stack.parseAndAnalyze("source", yul_source)) + return; + + yulAssert(stack.errors().empty(), "Parsed successfully but had errors."); + + if (!stack.parserResult()->code || !stack.parserResult()->analysisInfo) + return; + + // Optimize + stack.optimize(); +} \ No newline at end of file diff --git a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp new file mode 100644 index 000000000..0d210e092 --- /dev/null +++ b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp @@ -0,0 +1,75 @@ +/* + 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 . +*/ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace yul; +using namespace yul::test::yul_fuzzer; +using namespace std; + +using namespace langutil; +using namespace dev; +using namespace yul::test; + +DEFINE_BINARY_PROTO_FUZZER(Function const& _input) +{ + string yul_source = functionToString(_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(yul_source.data(), yul_source.size()); + } + + // AssemblyStack entry point + AssemblyStack stack(langutil::EVMVersion(), AssemblyStack::Language::StrictAssembly); + + try + { + // Parse protobuf mutated YUL code + if (!stack.parseAndAnalyze("source", yul_source) || !stack.parserResult()->code || + !stack.parserResult()->analysisInfo) + return; + } + catch (Exception const&) + { + return; + } + + ostringstream os1; + ostringstream os2; + yulFuzzerUtil::interpret(os1, stack.parserResult()->code); + stack.optimize(); + yulFuzzerUtil::interpret(os2, stack.parserResult()->code); + + bool isTraceEq = (os1.str() == os2.str()); + yulAssert(isTraceEq, "Interpreted traces for optimized and unoptimized code differ."); + return; +} \ No newline at end of file