Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ commands:
- test/tools/ossfuzz/strictasm_diff_ossfuzz
- test/tools/ossfuzz/strictasm_opt_ossfuzz
- test/tools/ossfuzz/yul_proto_diff_ossfuzz
- test/tools/ossfuzz/yul_proto_diff_ssa_cfg_ossfuzz
- test/tools/ossfuzz/yul_proto_diff_custom_mutate_ossfuzz
- test/tools/ossfuzz/yul_proto_ossfuzz
- test/tools/ossfuzz/sol_proto_ossfuzz
Expand Down
2 changes: 1 addition & 1 deletion scripts/ci/build_ossfuzz.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set -ex

ROOTDIR="$(realpath "$(dirname "$0")/../..")"
BUILDDIR="${ROOTDIR}/build"
BUILDDIR="${ROOTDIR}/build_ossfuzz"
mkdir -p "${BUILDDIR}" && mkdir -p "$BUILDDIR/deps"

function generate_protobuf_bindings
Expand Down
21 changes: 15 additions & 6 deletions scripts/docker/buildpack-deps/Dockerfile.ubuntu.clang.ossfuzz
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ RUN apt-get update; \
git \
jq \
libbz2-dev \
libc++-18-dev \
libc++abi-18-dev \
libc++-dev \
libc++abi-dev \
liblzma-dev \
libtool \
lsof \
Expand Down Expand Up @@ -67,6 +67,8 @@ RUN apt-get update; \
FROM base AS libraries

# Boost
# FIXME: OSSFuzz requires -nostdinc++ which needs explicit libc++ include paths.
# See boost workaround: https://github.com/google/oss-fuzz/blob/master/projects/boost/build.sh#L19 and https://github.com/llvm/llvm-project/issues/57104#issuecomment-1649525043
RUN set -ex; \
cd /usr/src; \
wget -q 'https://archives.boost.io/release/1.83.0/source/boost_1_83_0.tar.bz2' -O boost.tar.bz2; \
Expand All @@ -78,12 +80,12 @@ RUN set -ex; \
export LDFLAGS="$LDFLAGS -stdlib=libc++ -lpthread"; \
./bootstrap.sh --with-toolset=clang --prefix=/usr; \
./b2 toolset=clang \
cxxflags="${CXXFLAGS}" \
linkflags="${LDFLAGS}" \
cxxflags="$CXXFLAGS" \
linkflags="$LDFLAGS" \
headers; \
./b2 toolset=clang \
cxxflags="${CXXFLAGS}" \
linkflags="${LDFLAGS}" \
cxxflags="$CXXFLAGS" \
linkflags="$LDFLAGS" \
link=static variant=release runtime-link=static \
system filesystem unit_test_framework program_options \
install -j $(($(nproc)/2)); \
Expand Down Expand Up @@ -184,6 +186,13 @@ RUN set -ex; \
cp abicoder.hpp /usr/include; \
rm -rf /usr/src/Yul-Isabelle

# HEVM
RUN set -ex; \
hevm_version="0.56.0"; \
wget "https://github.com/ethereum/hevm/releases/download/release/${hevm_version}/hevm-x86_64-linux" -O /usr/bin/hevm; \
test "$(sha256sum /usr/bin/hevm)" = "aabc7570a987bb87f1f2628ea80e284ce251ce444f36940933a1d47151d5bf09 /usr/bin/hevm"; \
chmod +x /usr/bin/hevm

FROM base
COPY --from=libraries /usr/lib /usr/lib
COPY --from=libraries /usr/bin /usr/bin
Expand Down
4 changes: 3 additions & 1 deletion test/tools/fuzzer_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ void FuzzerUtil::testCompiler(
bool _optimize,
unsigned _rand,
bool _forceSMT,
bool _compileViaYul
bool _compileViaYul,
bool _ssaCfgCodegen
)
{
frontend::CompilerStack compiler;
Expand Down Expand Up @@ -112,6 +113,7 @@ void FuzzerUtil::testCompiler(
compiler.setEVMVersion(evmVersion);
compiler.setOptimiserSettings(optimiserSettings);
compiler.setViaIR(_compileViaYul);
compiler.setSSACFGCodegen(_ssaCfgCodegen);
try
{
compiler.compile();
Expand Down
3 changes: 2 additions & 1 deletion test/tools/fuzzer_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ struct FuzzerUtil
bool _optimize,
unsigned _rand,
bool _forceSMT,
bool _compileViaYul
bool _compileViaYul,
bool _ssaCfgCodegen = false
);
/// Adds the experimental SMTChecker pragma to each source file in the
/// source map.
Expand Down
17 changes: 17 additions & 0 deletions test/tools/ossfuzz/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ if (OSSFUZZ)
sol_proto_ossfuzz
yul_proto_ossfuzz
yul_proto_diff_ossfuzz
yul_proto_diff_ssa_cfg_ossfuzz
yul_proto_diff_custom_mutate_ossfuzz
stack_reuse_codegen_ossfuzz
)
Expand Down Expand Up @@ -85,6 +86,21 @@ if (OSSFUZZ)

target_compile_options(yul_proto_ossfuzz PUBLIC ${COMPILE_OPTIONS} ${SILENCE_PROTOBUF_AUTOGENERATED_WARNINGS})

add_executable(
yul_proto_diff_ssa_cfg_ossfuzz
yulProto_diff_ssa_cfg_ossfuzz.cpp
protoToYul.cpp
yulProto.pb.cc
)
target_include_directories(yul_proto_diff_ssa_cfg_ossfuzz PRIVATE /usr/include/libprotobuf-mutator)
target_link_libraries(yul_proto_diff_ssa_cfg_ossfuzz PRIVATE yul
protobuf-mutator-libfuzzer.a
protobuf-mutator.a
protobuf.a
)
set_target_properties(yul_proto_diff_ssa_cfg_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
target_compile_options(yul_proto_diff_ssa_cfg_ossfuzz PUBLIC ${COMPILE_OPTIONS} ${SILENCE_PROTOBUF_AUTOGENERATED_WARNINGS})

add_executable(
yul_proto_diff_ossfuzz
yulProto_diff_ossfuzz.cpp
Expand Down Expand Up @@ -203,6 +219,7 @@ if (OSSFUZZ)
)
set_target_properties(sol_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
target_compile_options(sol_proto_ossfuzz PUBLIC ${COMPILE_OPTIONS} ${SILENCE_PROTOBUF_AUTOGENERATED_WARNINGS})

else()
add_library(solc_ossfuzz
solc_ossfuzz.cpp
Expand Down
206 changes: 206 additions & 0 deletions test/tools/ossfuzz/yulProto_diff_ssa_cfg_ossfuzz.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/*
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 <fstream>

#include <boost/version.hpp>
#if (BOOST_VERSION < 108800)
#include <boost/process.hpp>
#else
#define BOOST_PROCESS_VERSION 1
#include <boost/process/v1/search_path.hpp>
#endif
#include <boost/filesystem.hpp>

#include <test/tools/ossfuzz/yulProto.pb.h>
#include <test/tools/ossfuzz/protoToYul.h>
#include <test/libyul/YulOptimizerTestCommon.h>

#include <src/libfuzzer/libfuzzer_macro.h>

#include <libyul/YulStack.h>
#include <libyul/Exceptions.h>

#include <liblangutil/DebugInfoSelection.h>
#include <liblangutil/EVMVersion.h>
#include <liblangutil/SourceReferenceFormatter.h>

using namespace solidity::util;
using namespace solidity::langutil;
using namespace solidity::yul;
using namespace solidity::yul::test;
using namespace solidity::yul::test::yul_fuzzer;

bool checkEquivalenceHEVM(
std::string const& bytecode1,
std::string const& bytecode2,
std::string const& yulSource)
{
namespace fs = boost::filesystem;

auto writeTempFile = [](std::string const& bytecode, std::string const& prefix) -> std::string {
std::string filename = (fs::temp_directory_path() / fs::unique_path(prefix + "-%%%%%%%%.bin")).string();
std::ofstream f(filename);
if (!f)
throw std::runtime_error("Failed to create temporary file: " + filename);
f << bytecode;
return filename;
};

std::string fileA = writeTempFile(bytecode1, "bytecode-a");
std::string fileB = writeTempFile(bytecode2, "bytecode-b");

boost::process::ipstream outStream, errStream;

std::vector<std::string> args = {
"equivalence",
"--code-a-file", fileA,
"--code-b-file", fileB,
"--smttimeout", "1",
"--num-solvers", "1",
"--only-deployed"
};

auto hevmPath = boost::process::search_path("hevm");
if (hevmPath.empty())
throw std::runtime_error("HEVM not found in PATH.");

boost::process::child hevmProcess(
hevmPath,
boost::process::args(args),
boost::process::std_out > outStream,
boost::process::std_err > errStream);

std::ostringstream outBuffer, errBuffer;
auto readStream = [](boost::process::ipstream& stream, std::ostringstream& buffer) {
std::string line;
while (std::getline(stream, line))
buffer << line << '\n';
};

std::thread outThread(readStream, std::ref(outStream), std::ref(outBuffer));
std::thread errThread(readStream, std::ref(errStream), std::ref(errBuffer));

hevmProcess.wait();
outThread.join();
errThread.join();

bool success = (hevmProcess.exit_code() == 0);
if (!success)
{
std::cout << "=== HEVM EQUIVALENCE CHECK FAILED ===" << std::endl;
std::cout << "Yul Source Input:\n" << yulSource << std::endl;
std::cout << "Bytecode length (Via-IR): " << bytecode1.length() << std::endl;
std::cout << "Bytecode length (SSA CFG): " << bytecode2.length() << std::endl;
std::cout << "HEVM output:\n" << outBuffer.str() << std::endl;
// FIXME: Hevm does not output to stderr in case of a mismatch, it outputs to stdout.
//std::cerr << "HEVM error:\n" << errBuffer.str() << std::endl;
std::cerr << "Bytecode files kept for analysis:\n Via-IR: " << fileA << "\n SSA CFG: " << fileB << std::endl;
}
else
{
fs::remove(fileA);
fs::remove(fileB);
}

return success;
}


DEFINE_PROTO_FUZZER(Program const& _input)
{
ProtoConverter converter;
std::string yul_source = converter.programToString(_input);
EVMVersion version = converter.version();

if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH"))
{
std::ofstream of(dump_path);
of.write(yul_source.data(), static_cast<std::streamsize>(yul_source.size()));
}

YulStringRepository::reset();

auto createParsedStack = [&]() -> YulStack {
YulStack stack(
version,
std::nullopt,
YulStack::Language::StrictAssembly,
solidity::frontend::OptimiserSettings::full(),
DebugInfoSelection::AllExceptExperimental()
);

if (
!stack.parseAndAnalyze("source", yul_source) ||
!stack.parserResult()->hasCode() ||
!stack.parserResult()->analysisInfo ||
Error::containsErrors(stack.errors())
)
{
SourceReferenceFormatter{std::cout, stack, false, false}.printErrorInformation(stack.errors());
yulAssert(false, "Proto fuzzer generated malformed program");
}
stack.optimize();
return stack;
};

auto assemble = [&](bool _ssaCfgCodegen) -> std::pair<MachineAssemblyObject, MachineAssemblyObject> {
YulStack stack = createParsedStack();
MachineAssemblyObject evmAsm, runtimeAsm;
std::tie(evmAsm, runtimeAsm) = stack.assembleWithDeployed({}, _ssaCfgCodegen);
return std::make_pair(std::move(evmAsm), std::move(runtimeAsm));
};

try
{
auto [evmAsm1, runtimeAsm1] = assemble(false); // Via-IR codegen
auto [evmAsm2, runtimeAsm2] = assemble(true); // SSA CFG codegen

auto checkEquivalence = [&](
std::string const& _kind,
auto const& _bytecode1,
auto const& _bytecode2
) {
if (_bytecode1 && _bytecode2)
{
std::string hex1 = _bytecode1->toHex();
std::string hex2 = _bytecode2->toHex();

if (hex1 == hex2)
return;

// If the bytecode differs, check equivalence using HEVM
if (!checkEquivalenceHEVM(hex1, hex2, yul_source))
throw std::runtime_error(_kind + " bytecode differs:\n"
"Via IR: " + hex1 + "\n"
"SSA CFG: " + hex2);
}
};

checkEquivalence("Object", evmAsm1.bytecode, evmAsm2.bytecode);
checkEquivalence("Runtime Object", runtimeAsm1.bytecode, runtimeAsm2.bytecode);
}
catch (std::runtime_error const& e)
{
std::cout << "Error: " << e.what() << std::endl;
std::cout << "EVM Version: " << version.name() << std::endl;
yulAssert(false, "Bytecode differ between SSA CFG and IR codegen.");
}

return;
}