Skip to content

Commit b674581

Browse files
committed
Add fuzzer to ssa cfg pipeline
1 parent 994b602 commit b674581

File tree

7 files changed

+245
-9
lines changed

7 files changed

+245
-9
lines changed

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ commands:
306306
- test/tools/ossfuzz/strictasm_diff_ossfuzz
307307
- test/tools/ossfuzz/strictasm_opt_ossfuzz
308308
- test/tools/ossfuzz/yul_proto_diff_ossfuzz
309+
- test/tools/ossfuzz/yul_proto_diff_ssa_cfg_ossfuzz
309310
- test/tools/ossfuzz/yul_proto_diff_custom_mutate_ossfuzz
310311
- test/tools/ossfuzz/yul_proto_ossfuzz
311312
- test/tools/ossfuzz/sol_proto_ossfuzz

scripts/ci/build_ossfuzz.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
set -ex
33

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

88
function generate_protobuf_bindings

scripts/docker/buildpack-deps/Dockerfile.ubuntu.clang.ossfuzz

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ RUN apt-get update; \
3535
git \
3636
jq \
3737
libbz2-dev \
38-
libc++-18-dev \
39-
libc++abi-18-dev \
38+
libc++-dev \
39+
libc++abi-dev \
4040
liblzma-dev \
4141
libtool \
4242
lsof \
@@ -67,6 +67,8 @@ RUN apt-get update; \
6767
FROM base AS libraries
6868

6969
# Boost
70+
# FIXME: OSSFuzz requires -nostdinc++ which needs explicit libc++ include paths.
71+
# 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
7072
RUN set -ex; \
7173
cd /usr/src; \
7274
wget -q 'https://archives.boost.io/release/1.83.0/source/boost_1_83_0.tar.bz2' -O boost.tar.bz2; \
@@ -78,12 +80,12 @@ RUN set -ex; \
7880
export LDFLAGS="$LDFLAGS -stdlib=libc++ -lpthread"; \
7981
./bootstrap.sh --with-toolset=clang --prefix=/usr; \
8082
./b2 toolset=clang \
81-
cxxflags="${CXXFLAGS}" \
82-
linkflags="${LDFLAGS}" \
83+
cxxflags="$CXXFLAGS" \
84+
linkflags="$LDFLAGS" \
8385
headers; \
8486
./b2 toolset=clang \
85-
cxxflags="${CXXFLAGS}" \
86-
linkflags="${LDFLAGS}" \
87+
cxxflags="$CXXFLAGS" \
88+
linkflags="$LDFLAGS" \
8789
link=static variant=release runtime-link=static \
8890
system filesystem unit_test_framework program_options \
8991
install -j $(($(nproc)/2)); \
@@ -184,6 +186,13 @@ RUN set -ex; \
184186
cp abicoder.hpp /usr/include; \
185187
rm -rf /usr/src/Yul-Isabelle
186188

189+
# HEVM
190+
RUN set -ex; \
191+
hevm_version="0.56.0"; \
192+
wget "https://github.com/ethereum/hevm/releases/download/release/${hevm_version}/hevm-x86_64-linux" -O /usr/bin/hevm; \
193+
test "$(sha256sum /usr/bin/hevm)" = "aabc7570a987bb87f1f2628ea80e284ce251ce444f36940933a1d47151d5bf09 /usr/bin/hevm"; \
194+
chmod +x /usr/bin/hevm
195+
187196
FROM base
188197
COPY --from=libraries /usr/lib /usr/lib
189198
COPY --from=libraries /usr/bin /usr/bin

test/tools/fuzzer_common.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ void FuzzerUtil::testCompiler(
7979
bool _optimize,
8080
unsigned _rand,
8181
bool _forceSMT,
82-
bool _compileViaYul
82+
bool _compileViaYul,
83+
bool _ssaCfgCodegen
8384
)
8485
{
8586
frontend::CompilerStack compiler;
@@ -112,6 +113,7 @@ void FuzzerUtil::testCompiler(
112113
compiler.setEVMVersion(evmVersion);
113114
compiler.setOptimiserSettings(optimiserSettings);
114115
compiler.setViaIR(_compileViaYul);
116+
compiler.setSSACFGCodegen(_ssaCfgCodegen);
115117
try
116118
{
117119
compiler.compile();

test/tools/fuzzer_common.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ struct FuzzerUtil
4242
bool _optimize,
4343
unsigned _rand,
4444
bool _forceSMT,
45-
bool _compileViaYul
45+
bool _compileViaYul,
46+
bool _ssaCfgCodegen = false
4647
);
4748
/// Adds the experimental SMTChecker pragma to each source file in the
4849
/// source map.

test/tools/ossfuzz/CMakeLists.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ if (OSSFUZZ)
1414
sol_proto_ossfuzz
1515
yul_proto_ossfuzz
1616
yul_proto_diff_ossfuzz
17+
yul_proto_diff_ssa_cfg_ossfuzz
1718
yul_proto_diff_custom_mutate_ossfuzz
1819
stack_reuse_codegen_ossfuzz
1920
)
@@ -85,6 +86,21 @@ if (OSSFUZZ)
8586

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

89+
add_executable(
90+
yul_proto_diff_ssa_cfg_ossfuzz
91+
yulProto_diff_ssa_cfg_ossfuzz.cpp
92+
protoToYul.cpp
93+
yulProto.pb.cc
94+
)
95+
target_include_directories(yul_proto_diff_ssa_cfg_ossfuzz PRIVATE /usr/include/libprotobuf-mutator)
96+
target_link_libraries(yul_proto_diff_ssa_cfg_ossfuzz PRIVATE yul
97+
protobuf-mutator-libfuzzer.a
98+
protobuf-mutator.a
99+
protobuf.a
100+
)
101+
set_target_properties(yul_proto_diff_ssa_cfg_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
102+
target_compile_options(yul_proto_diff_ssa_cfg_ossfuzz PUBLIC ${COMPILE_OPTIONS} ${SILENCE_PROTOBUF_AUTOGENERATED_WARNINGS})
103+
88104
add_executable(
89105
yul_proto_diff_ossfuzz
90106
yulProto_diff_ossfuzz.cpp
@@ -203,6 +219,7 @@ if (OSSFUZZ)
203219
)
204220
set_target_properties(sol_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
205221
target_compile_options(sol_proto_ossfuzz PUBLIC ${COMPILE_OPTIONS} ${SILENCE_PROTOBUF_AUTOGENERATED_WARNINGS})
222+
206223
else()
207224
add_library(solc_ossfuzz
208225
solc_ossfuzz.cpp
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
19+
#include <fstream>
20+
21+
#include <boost/version.hpp>
22+
#if (BOOST_VERSION < 108800)
23+
#include <boost/process.hpp>
24+
#else
25+
#define BOOST_PROCESS_VERSION 1
26+
#include <boost/process/v1/search_path.hpp>
27+
#endif
28+
#include <boost/filesystem.hpp>
29+
30+
#include <test/tools/ossfuzz/yulProto.pb.h>
31+
#include <test/tools/ossfuzz/protoToYul.h>
32+
#include <test/libyul/YulOptimizerTestCommon.h>
33+
34+
#include <src/libfuzzer/libfuzzer_macro.h>
35+
36+
#include <libyul/YulStack.h>
37+
#include <libyul/Exceptions.h>
38+
39+
#include <liblangutil/DebugInfoSelection.h>
40+
#include <liblangutil/EVMVersion.h>
41+
#include <liblangutil/SourceReferenceFormatter.h>
42+
43+
using namespace solidity::util;
44+
using namespace solidity::langutil;
45+
using namespace solidity::yul;
46+
using namespace solidity::yul::test;
47+
using namespace solidity::yul::test::yul_fuzzer;
48+
49+
bool checkEquivalenceHEVM(
50+
std::string const& bytecode1,
51+
std::string const& bytecode2,
52+
std::string const& yulSource)
53+
{
54+
namespace fs = boost::filesystem;
55+
56+
auto writeTempFile = [](std::string const& bytecode, std::string const& prefix) -> std::string {
57+
std::string filename = (fs::temp_directory_path() / fs::unique_path(prefix + "-%%%%%%%%.bin")).string();
58+
std::ofstream f(filename);
59+
if (!f)
60+
throw std::runtime_error("Failed to create temporary file: " + filename);
61+
f << bytecode;
62+
return filename;
63+
};
64+
65+
std::string fileA = writeTempFile(bytecode1, "bytecode-a");
66+
std::string fileB = writeTempFile(bytecode2, "bytecode-b");
67+
68+
boost::process::ipstream outStream, errStream;
69+
70+
std::vector<std::string> args = {
71+
"equivalence",
72+
"--code-a-file", fileA,
73+
"--code-b-file", fileB,
74+
"--smttimeout", "1",
75+
"--num-solvers", "1",
76+
"--only-deployed"
77+
};
78+
79+
auto hevmPath = boost::process::search_path("hevm");
80+
if (hevmPath.empty())
81+
throw std::runtime_error("HEVM not found in PATH.");
82+
83+
boost::process::child hevmProcess(
84+
hevmPath,
85+
boost::process::args(args),
86+
boost::process::std_out > outStream,
87+
boost::process::std_err > errStream);
88+
89+
std::ostringstream outBuffer, errBuffer;
90+
auto readStream = [](boost::process::ipstream& stream, std::ostringstream& buffer) {
91+
std::string line;
92+
while (std::getline(stream, line))
93+
buffer << line << '\n';
94+
};
95+
96+
std::thread outThread(readStream, std::ref(outStream), std::ref(outBuffer));
97+
std::thread errThread(readStream, std::ref(errStream), std::ref(errBuffer));
98+
99+
hevmProcess.wait();
100+
outThread.join();
101+
errThread.join();
102+
103+
bool success = (hevmProcess.exit_code() == 0);
104+
if (!success)
105+
{
106+
std::cout << "=== HEVM EQUIVALENCE CHECK FAILED ===" << std::endl;
107+
std::cout << "Yul Source Input:\n" << yulSource << std::endl;
108+
std::cout << "Bytecode length (Via-IR): " << bytecode1.length() << std::endl;
109+
std::cout << "Bytecode length (SSA CFG): " << bytecode2.length() << std::endl;
110+
std::cout << "HEVM output:\n" << outBuffer.str() << std::endl;
111+
// FIXME: Hevm does not output to stderr in case of a mismatch, it outputs to stdout.
112+
//std::cerr << "HEVM error:\n" << errBuffer.str() << std::endl;
113+
std::cerr << "Bytecode files kept for analysis:\n Via-IR: " << fileA << "\n SSA CFG: " << fileB << std::endl;
114+
}
115+
else
116+
{
117+
fs::remove(fileA);
118+
fs::remove(fileB);
119+
}
120+
121+
return success;
122+
}
123+
124+
125+
DEFINE_PROTO_FUZZER(Program const& _input)
126+
{
127+
ProtoConverter converter;
128+
std::string yul_source = converter.programToString(_input);
129+
EVMVersion version = converter.version();
130+
131+
if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH"))
132+
{
133+
std::ofstream of(dump_path);
134+
of.write(yul_source.data(), static_cast<std::streamsize>(yul_source.size()));
135+
}
136+
137+
YulStringRepository::reset();
138+
139+
auto createParsedStack = [&]() -> YulStack {
140+
YulStack stack(
141+
version,
142+
std::nullopt,
143+
YulStack::Language::StrictAssembly,
144+
solidity::frontend::OptimiserSettings::full(),
145+
DebugInfoSelection::AllExceptExperimental()
146+
);
147+
148+
if (
149+
!stack.parseAndAnalyze("source", yul_source) ||
150+
!stack.parserResult()->hasCode() ||
151+
!stack.parserResult()->analysisInfo ||
152+
Error::containsErrors(stack.errors())
153+
)
154+
{
155+
SourceReferenceFormatter{std::cout, stack, false, false}.printErrorInformation(stack.errors());
156+
yulAssert(false, "Proto fuzzer generated malformed program");
157+
}
158+
stack.optimize();
159+
return stack;
160+
};
161+
162+
auto assemble = [&](bool _ssaCfgCodegen) -> std::pair<MachineAssemblyObject, MachineAssemblyObject> {
163+
YulStack stack = createParsedStack();
164+
MachineAssemblyObject evmAsm, runtimeAsm;
165+
std::tie(evmAsm, runtimeAsm) = stack.assembleWithDeployed({}, _ssaCfgCodegen);
166+
return std::make_pair(std::move(evmAsm), std::move(runtimeAsm));
167+
};
168+
169+
try
170+
{
171+
auto [evmAsm1, runtimeAsm1] = assemble(false); // Via-IR codegen
172+
auto [evmAsm2, runtimeAsm2] = assemble(true); // SSA CFG codegen
173+
174+
auto checkEquivalence = [&](
175+
std::string const& _kind,
176+
auto const& _bytecode1,
177+
auto const& _bytecode2
178+
) {
179+
if (_bytecode1 && _bytecode2)
180+
{
181+
std::string hex1 = _bytecode1->toHex();
182+
std::string hex2 = _bytecode2->toHex();
183+
184+
if (hex1 == hex2)
185+
return;
186+
187+
// If the bytecode differs, check equivalence using HEVM
188+
if (!checkEquivalenceHEVM(hex1, hex2, yul_source))
189+
throw std::runtime_error(_kind + " bytecode differs:\n"
190+
"Via IR: " + hex1 + "\n"
191+
"SSA CFG: " + hex2);
192+
}
193+
};
194+
195+
checkEquivalence("Object", evmAsm1.bytecode, evmAsm2.bytecode);
196+
checkEquivalence("Runtime Object", runtimeAsm1.bytecode, runtimeAsm2.bytecode);
197+
}
198+
catch (std::runtime_error const& e)
199+
{
200+
std::cout << "Error: " << e.what() << std::endl;
201+
std::cout << "EVM Version: " << version.name() << std::endl;
202+
yulAssert(false, "Bytecode differ between SSA CFG and IR codegen.");
203+
}
204+
205+
return;
206+
}

0 commit comments

Comments
 (0)