Skip to content

Commit c48be40

Browse files
authored
Merge pull request #13380 from ethereum/multiple-indirections
Permit multiple indirections in coding calldata to and from memory/calldata
2 parents 51e2259 + e3ed29d commit c48be40

File tree

8 files changed

+138
-93
lines changed

8 files changed

+138
-93
lines changed

test/tools/ossfuzz/AbiV2IsabelleFuzzer.cpp

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ static evmc::VM evmone = evmc::VM{evmc_create_evmone()};
3636

3737
DEFINE_PROTO_FUZZER(Contract const& _contract)
3838
{
39-
ProtoConverter converter;
39+
ProtoConverter converter(_contract.seed());
4040
string contractSource = converter.contractToString(_contract);
4141

4242
if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH"))
@@ -69,14 +69,11 @@ DEFINE_PROTO_FUZZER(Contract const& _contract)
6969
{}
7070
);
7171
auto result = evmoneUtil.compileDeployAndExecute(encodedData);
72-
if (result.has_value())
73-
{
74-
solAssert(result->status_code != EVMC_REVERT, "Proto ABIv2 fuzzer: EVM One reverted.");
75-
if (result->status_code == EVMC_SUCCESS)
76-
solAssert(
77-
EvmoneUtility::zeroWord(result->output_data, result->output_size),
78-
"Proto ABIv2 fuzzer: ABIv2 coding failure found."
79-
);
80-
}
72+
solAssert(result.status_code != EVMC_REVERT, "Proto ABIv2 fuzzer: EVM One reverted.");
73+
if (result.status_code == EVMC_SUCCESS)
74+
solAssert(
75+
EvmoneUtility::zeroWord(result.output_data, result.output_size),
76+
"Proto ABIv2 fuzzer: ABIv2 coding failure found."
77+
);
8178
}
8279
}

test/tools/ossfuzz/SolidityEvmoneInterface.cpp

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -128,59 +128,51 @@ evmc::result EvmoneUtility::deployAndExecute(
128128
return callResult;
129129
}
130130

131-
optional<evmc::result> EvmoneUtility::compileDeployAndExecute(string _fuzzIsabelle)
131+
evmc::result EvmoneUtility::compileDeployAndExecute(string _fuzzIsabelle)
132132
{
133133
map<string, h160> libraryAddressMap;
134134
// Stage 1: Compile and deploy library if present.
135135
if (!m_libraryName.empty())
136136
{
137137
m_compilationFramework.contractName(m_libraryName);
138138
auto compilationOutput = m_compilationFramework.compileContract();
139-
if (compilationOutput.has_value())
140-
{
141-
CompilerOutput cOutput = compilationOutput.value();
142-
// Deploy contract and signal failure if deploy failed
143-
evmc::result createResult = deployContract(cOutput.byteCode);
144-
solAssert(
145-
createResult.status_code == EVMC_SUCCESS,
146-
"SolidityEvmoneInterface: Library deployment failed"
147-
);
148-
libraryAddressMap[m_libraryName] = EVMHost::convertFromEVMC(createResult.create_address);
149-
m_compilationFramework.libraryAddresses(libraryAddressMap);
150-
}
151-
else
152-
return {};
139+
solAssert(compilationOutput.has_value(), "Compiling library failed");
140+
CompilerOutput cOutput = compilationOutput.value();
141+
// Deploy contract and signal failure if deploy failed
142+
evmc::result createResult = deployContract(cOutput.byteCode);
143+
solAssert(
144+
createResult.status_code == EVMC_SUCCESS,
145+
"SolidityEvmoneInterface: Library deployment failed"
146+
);
147+
libraryAddressMap[m_libraryName] = EVMHost::convertFromEVMC(createResult.create_address);
148+
m_compilationFramework.libraryAddresses(libraryAddressMap);
153149
}
154150

155151
// Stage 2: Compile, deploy, and execute contract, optionally using library
156152
// address map.
157153
m_compilationFramework.contractName(m_contractName);
158154
auto cOutput = m_compilationFramework.compileContract();
159-
if (cOutput.has_value())
160-
{
161-
solAssert(
162-
!cOutput->byteCode.empty() && !cOutput->methodIdentifiersInContract.empty(),
163-
"SolidityEvmoneInterface: Invalid compilation output."
164-
);
165-
166-
string methodName;
167-
if (!_fuzzIsabelle.empty())
168-
// TODO: Remove this once a cleaner solution is found for querying
169-
// isabelle test entry point. At the moment, we are sure that the
170-
// entry point is the second method in the contract (hence the ++)
171-
// but not its name.
172-
methodName = (++cOutput->methodIdentifiersInContract.begin())->asString() +
173-
_fuzzIsabelle.substr(2, _fuzzIsabelle.size());
174-
else
175-
methodName = cOutput->methodIdentifiersInContract[m_methodName].asString();
155+
solAssert(cOutput.has_value(), "Compiling contract failed");
156+
solAssert(
157+
!cOutput->byteCode.empty() && !cOutput->methodIdentifiersInContract.empty(),
158+
"SolidityEvmoneInterface: Invalid compilation output."
159+
);
176160

177-
return deployAndExecute(
178-
cOutput->byteCode,
179-
methodName
180-
);
181-
}
161+
string methodName;
162+
if (!_fuzzIsabelle.empty())
163+
// TODO: Remove this once a cleaner solution is found for querying
164+
// isabelle test entry point. At the moment, we are sure that the
165+
// entry point is the second method in the contract (hence the ++)
166+
// but not its name.
167+
methodName = (++cOutput->methodIdentifiersInContract.begin())->asString() +
168+
_fuzzIsabelle.substr(2, _fuzzIsabelle.size());
182169
else
183-
return {};
170+
methodName = cOutput->methodIdentifiersInContract[m_methodName].asString();
171+
172+
return deployAndExecute(
173+
cOutput->byteCode,
174+
methodName
175+
);
184176
}
185177

186178
optional<CompilerOutput> EvmoneUtility::compileContract()

test/tools/ossfuzz/SolidityEvmoneInterface.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ class EvmoneUtility
122122
/// and executing test configuration.
123123
/// @param _isabelleData contains encoding data to be passed to the
124124
/// isabelle test entry point.
125-
std::optional<evmc::result> compileDeployAndExecute(std::string _isabelleData = {});
125+
evmc::result compileDeployAndExecute(std::string _isabelleData = {});
126126
/// Compares the contents of the memory address pointed to
127127
/// by `_result` of `_length` bytes to u256 zero.
128128
/// @returns true if `_result` is zero, false

test/tools/ossfuzz/abiV2Proto.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ message Contract {
9292
required VarDecl state_vars = 1;
9393
required TestFunction testfunction = 2;
9494
required Test test = 3;
95+
required uint32 seed = 4;
9596
}
9697

9798
package solidity.test.abiv2fuzzer;

test/tools/ossfuzz/abiV2ProtoFuzzer.cpp

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ static evmc::VM evmone = evmc::VM{evmc_create_evmone()};
3535

3636
DEFINE_PROTO_FUZZER(Contract const& _input)
3737
{
38-
string contract_source = ProtoConverter{}.contractToString(_input);
38+
string contract_source = ProtoConverter{_input.seed()}.contractToString(_input);
3939

4040
if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH"))
4141
{
@@ -61,21 +61,18 @@ DEFINE_PROTO_FUZZER(Contract const& _input)
6161
);
6262
// Invoke test function
6363
auto result = evmoneUtil.compileDeployAndExecute();
64-
if (result.has_value())
65-
{
66-
// We don't care about EVM One failures other than EVMC_REVERT
67-
solAssert(result->status_code != EVMC_REVERT, "Proto ABIv2 fuzzer: EVM One reverted");
68-
if (result->status_code == EVMC_SUCCESS)
69-
if (!EvmoneUtility::zeroWord(result->output_data, result->output_size))
70-
{
71-
solidity::bytes resultAsBytes;
72-
for (size_t i = 0; i < result->output_size; i++)
73-
resultAsBytes.push_back(result->output_data[i]);
74-
cout << solidity::util::toHex(resultAsBytes) << endl;
75-
solAssert(
76-
false,
77-
"Proto ABIv2 fuzzer: ABIv2 coding failure found"
78-
);
79-
}
80-
}
64+
// We don't care about EVM One failures other than EVMC_REVERT
65+
solAssert(result.status_code != EVMC_REVERT, "Proto ABIv2 fuzzer: EVM One reverted");
66+
if (result.status_code == EVMC_SUCCESS)
67+
if (!EvmoneUtility::zeroWord(result.output_data, result.output_size))
68+
{
69+
solidity::bytes res;
70+
for (size_t i = 0; i < result.output_size; i++)
71+
res.push_back(result.output_data[i]);
72+
cout << solidity::util::toHex(res) << endl;
73+
solAssert(
74+
false,
75+
"Proto ABIv2 fuzzer: ABIv2 coding failure found"
76+
);
77+
}
8178
}

test/tools/ossfuzz/protoToAbiV2.cpp

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -416,16 +416,7 @@ void ProtoConverter::appendTypedParamsExternal(
416416
Delimiter _delimiter
417417
)
418418
{
419-
std::string qualifiedTypeString = (
420-
_isValueType ?
421-
_typeString :
422-
_typeString + " calldata"
423-
);
424-
m_typedParamsExternal << Whiskers(R"(<delimiter><type> <varName>)")
425-
("delimiter", delimiterToString(_delimiter))
426-
("type", qualifiedTypeString)
427-
("varName", _varName)
428-
.render();
419+
m_externalParamsRep.push_back({_delimiter, _isValueType, _typeString, _varName});
429420
m_untypedParamsExternal << Whiskers(R"(<delimiter><varName>)")
430421
("delimiter", delimiterToString(_delimiter))
431422
("varName", _varName)
@@ -475,7 +466,25 @@ std::string ProtoConverter::typedParametersAsString(CalleeType _calleeType)
475466
case CalleeType::PUBLIC:
476467
return m_typedParamsPublic.str();
477468
case CalleeType::EXTERNAL:
478-
return m_typedParamsExternal.str();
469+
{
470+
ostringstream typedParamsExternal;
471+
for (auto const& i: m_externalParamsRep)
472+
{
473+
Delimiter del = get<0>(i);
474+
bool valueType = get<1>(i);
475+
string typeString = get<2>(i);
476+
string varName = get<3>(i);
477+
bool isCalldata = randomBool(/*probability=*/0.5);
478+
string location = (isCalldata ? "calldata" : "memory");
479+
string qualifiedTypeString = (valueType ? typeString : typeString + " " + location);
480+
typedParamsExternal << Whiskers(R"(<delimiter><type> <varName>)")
481+
("delimiter", delimiterToString(del))
482+
("type", qualifiedTypeString)
483+
("varName", varName)
484+
.render();
485+
}
486+
return typedParamsExternal.str();
487+
}
479488
}
480489
}
481490

@@ -666,6 +675,33 @@ string ProtoConverter::calldataHelperFunctions()
666675
return 0;
667676
})";
668677

678+
/// These are indirections to test memory-calldata codings more robustly.
679+
stringstream indirections;
680+
unsigned numIndirections = randomNumberOneToN(s_maxIndirections);
681+
for (unsigned i = 1; i <= numIndirections; i++)
682+
{
683+
bool finalIndirection = i == numIndirections;
684+
string mutability = (finalIndirection ? "pure" : "view");
685+
indirections << Whiskers(R"(
686+
function coder_calldata_external_i<N>(<parameters>) external <mutability> returns (uint) {
687+
<?finalIndirection>
688+
<equality_checks>
689+
return 0;
690+
<!finalIndirection>
691+
return this.coder_calldata_external_i<NPlusOne>(<untyped_parameters>);
692+
</finalIndirection>
693+
}
694+
)")
695+
("N", to_string(i))
696+
("parameters", typedParametersAsString(CalleeType::EXTERNAL))
697+
("mutability", mutability)
698+
("finalIndirection", finalIndirection)
699+
("equality_checks", equalityChecksAsString())
700+
("NPlusOne", to_string(i + 1))
701+
("untyped_parameters", m_untypedParamsExternal.str())
702+
.render();
703+
}
704+
669705
// These are callee functions that encode from storage, decode to
670706
// memory/calldata and check if decoded value matches storage value
671707
// return true on successful match, false otherwise
@@ -676,18 +712,15 @@ string ProtoConverter::calldataHelperFunctions()
676712
}
677713
678714
function coder_calldata_external(<parameters_calldata>) external view returns (uint) {
679-
return this.coder_calldata_external_indirection(<untyped_parameters>);
680-
}
681-
682-
function coder_calldata_external_indirection(<parameters_calldata>) external pure returns (uint) {
683-
<equality_checks>
684-
return 0;
715+
return this.coder_calldata_external_i1(<untyped_parameters>);
685716
}
717+
<indirections>
686718
)")
687719
("parameters_memory", typedParametersAsString(CalleeType::PUBLIC))
688720
("equality_checks", equalityChecksAsString())
689721
("parameters_calldata", typedParametersAsString(CalleeType::EXTERNAL))
690722
("untyped_parameters", m_untypedParamsExternal.str())
723+
("indirections", indirections.str())
691724
.render();
692725

693726
return calldataHelperFuncs.str();

test/tools/ossfuzz/protoToAbiV2.h

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <boost/variant.hpp>
1616

1717
#include <ostream>
18+
#include <random>
1819
#include <sstream>
1920

2021
/**
@@ -134,21 +135,26 @@
134135

135136
namespace solidity::test::abiv2fuzzer
136137
{
138+
using RandomEngine = std::mt19937_64;
139+
using Distribution = std::uniform_int_distribution<unsigned>;
140+
using Bernoulli = std::bernoulli_distribution;
137141

138142
/// Converts a protobuf input into a Solidity program that tests
139143
/// abi coding.
140144
class ProtoConverter
141145
{
142146
public:
143-
ProtoConverter():
147+
ProtoConverter(unsigned _seed):
144148
m_isStateVar(true),
145149
m_counter(0),
146150
m_varCounter(0),
147151
m_returnValue(1),
148152
m_isLastDynParamRightPadded(false),
149153
m_structCounter(0),
150154
m_numStructsAdded(0)
151-
{}
155+
{
156+
m_random = std::make_unique<RandomEngine>(_seed);
157+
}
152158

153159
ProtoConverter(ProtoConverter const&) = delete;
154160
ProtoConverter(ProtoConverter&&) = delete;
@@ -173,6 +179,13 @@ class ProtoConverter
173179
EXTERNAL
174180
};
175181

182+
/// Each external parameter representation contains the following:
183+
/// - Delimiter prefix
184+
/// - Boolean that is true if value type, false otherwise
185+
/// - String representation of type
186+
/// - Parameter name
187+
using ParameterPack = std::tuple<Delimiter, bool, std::string, std::string>;
188+
176189
/// Visitors for various Protobuf types
177190
/// Visit top-level contract specification
178191
void visit(Contract const&);
@@ -381,14 +394,23 @@ class ProtoConverter
381394

382395
/// Convert delimter to a comma or null string.
383396
static std::string delimiterToString(Delimiter _delimiter, bool _space = true);
397+
/// Generates number in the range [1, @param _n] uniformly at random.
398+
unsigned randomNumberOneToN(unsigned _n)
399+
{
400+
return Distribution(1, _n)(*m_random);
401+
}
402+
/// Generates boolean that has a bernoulli distribution defined by @param _p.
403+
bool randomBool(double _p)
404+
{
405+
return Bernoulli{_p}(*m_random);
406+
}
384407

385408
/// Contains the test program
386409
std::ostringstream m_output;
387410
/// Contains a subset of the test program. This subset contains
388411
/// checks to be encoded in the test program
389412
std::ostringstream m_checks;
390413
/// Contains typed parameter list to be passed to callee functions
391-
std::ostringstream m_typedParamsExternal;
392414
std::ostringstream m_typedParamsPublic;
393415
/// Contains parameter list to be passed to callee functions
394416
std::ostringstream m_untypedParamsExternal;
@@ -418,10 +440,16 @@ class ProtoConverter
418440
unsigned m_numStructsAdded;
419441
/// Enum stating abiv2 coder to be tested
420442
Contract_Test m_test;
443+
/// Representation of external parameters
444+
std::vector<ParameterPack> m_externalParamsRep;
445+
/// Random number generator
446+
std::unique_ptr<RandomEngine> m_random;
421447
/// Prefixes for declared and parameterized variable names
422448
static auto constexpr s_localVarNamePrefix = "lv_";
423449
static auto constexpr s_stateVarNamePrefix = "sv_";
424450
static auto constexpr s_paramNamePrefix = "p_";
451+
/// Maximum number of indirections to test calldata coding
452+
static unsigned constexpr s_maxIndirections = 5;
425453
};
426454

427455
/// Visitor interface for Solidity protobuf types.

test/tools/ossfuzz/solProtoFuzzer.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,10 @@ DEFINE_PROTO_FUZZER(Program const& _input)
7979
methodName
8080
);
8181
auto minimalResult = evmoneUtil.compileDeployAndExecute();
82-
if (minimalResult.has_value())
83-
{
84-
solAssert(minimalResult->status_code != EVMC_REVERT, "Sol proto fuzzer: Evmone reverted.");
85-
if (minimalResult->status_code == EVMC_SUCCESS)
82+
solAssert(minimalResult.status_code != EVMC_REVERT, "Sol proto fuzzer: Evmone reverted.");
83+
if (minimalResult.status_code == EVMC_SUCCESS)
8684
solAssert(
87-
EvmoneUtility::zeroWord(minimalResult->output_data, minimalResult->output_size),
85+
EvmoneUtility::zeroWord(minimalResult.output_data, minimalResult.output_size),
8886
"Proto solc fuzzer: Output incorrect"
8987
);
90-
}
9188
}

0 commit comments

Comments
 (0)