Merge pull request #7177 from ethereum/abiv2-test-decoding-of-invalid-encode

Test low level calls with correct and incorrect encodings
This commit is contained in:
Bhargava Shastry 2019-08-06 12:06:13 +02:00 committed by GitHub
commit efa2648771
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 99 additions and 10 deletions

View File

@ -102,6 +102,8 @@ message VarDecl {
message TestFunction {
required VarDecl local_vars = 1;
// Length of invalid encoding
required uint64 invalid_encoding_length = 2;
}
message Contract {

View File

@ -35,10 +35,10 @@ namespace
{
/// Test function returns a uint256 value
static size_t const expectedOutputLength = 32;
/// Expected output value is decimal 1000 or hex 03E8
/// Expected output value is decimal 0
static uint8_t const expectedOutput[expectedOutputLength] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, u'\x03', u'\xe8'
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
/// Compares the contents of the memory address pointed to
@ -123,11 +123,16 @@ DEFINE_PROTO_FUZZER(Contract const& _input)
// We always call the function test() that is defined in proto converter template
hexEncodedInput = methodIdentifiers["test()"].asString();
}
// Ignore compilation failures
catch (Exception const&)
// Ignore stack too deep errors during compilation
catch (eth::StackTooDeepException const&)
{
return;
}
// Do not ignore other compilation failures
catch (Exception const&)
{
throw;
}
if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_CODE"))
{

View File

@ -690,10 +690,25 @@ void ProtoConverter::visit(TestFunction const& _x)
uint returnVal = this.coder_public(<parameter_names>);
if (returnVal != 0)
return returnVal;
return (uint(1000) + this.coder_external(<parameter_names>));
returnVal = this.coder_external(<parameter_names>);
if (returnVal != 0)
return uint(200000) + returnVal;
bytes memory argumentEncoding = abi.encode(<parameter_names>);
returnVal = checkEncodedCall(this.coder_public.selector, argumentEncoding, <invalidLengthFuzz>);
if (returnVal != 0)
return returnVal;
returnVal = checkEncodedCall(this.coder_external.selector, argumentEncoding, <invalidLengthFuzz>);
if (returnVal != 0)
return uint(200000) + returnVal;
return 0;
}
)")
("parameter_names", dev::suffixedVariableNameList(s_varNamePrefix, 0, m_varCounter))
("invalidLengthFuzz", std::to_string(_x.invalid_encoding_length()))
.render();
}
@ -708,6 +723,42 @@ void ProtoConverter::writeHelperFunctions()
return false;
return true;
}
/// Accepts function selector, correct argument encoding, and length of invalid encoding and returns
/// the correct and incorrect abi encoding for calling the function specified by the function selector.
function createEncoding(bytes4 funcSelector, bytes memory argumentEncoding, uint invalidLengthFuzz)
internal pure returns (bytes memory, bytes memory)
{
bytes memory validEncoding = new bytes(4 + argumentEncoding.length);
// Ensure that invalidEncoding crops at least one and at most all bytes from correct encoding.
uint invalidLength = invalidLengthFuzz % argumentEncoding.length;
bytes memory invalidEncoding = new bytes(4 + invalidLength);
for (uint i = 0; i < 4; i++)
validEncoding[i] = invalidEncoding[i] = funcSelector[i];
for (uint i = 0; i < argumentEncoding.length; i++)
validEncoding[i+4] = argumentEncoding[i];
for (uint i = 0; i < invalidLength; i++)
invalidEncoding[i+4] = argumentEncoding[i];
return (validEncoding, invalidEncoding);
}
/// Accepts function selector, correct argument encoding, and an invalid encoding length as input.
/// Returns a non-zero value if either call with correct encoding fails or call with incorrect encoding
/// succeeds. Returns zero if both calls meet expectation.
function checkEncodedCall(bytes4 funcSelector, bytes memory argumentEncoding, uint invalidLengthFuzz) public returns (uint)
{
(bytes memory validEncoding, bytes memory invalidEncoding) = createEncoding(funcSelector, argumentEncoding, invalidLengthFuzz);
(bool success, bytes memory returnVal) = address(this).call(validEncoding);
uint returnCode = abi.decode(returnVal, (uint));
// Return non-zero value if call fails for correct encoding
if (success == false || returnCode != 0)
return 400000;
(success, ) = address(this).call(invalidEncoding);
// Return non-zero value if call succeeds for incorrect encoding
if (success == true)
return 400001;
return 0;
}
)";
// These are callee functions that encode from storage, decode to

View File

@ -26,11 +26,42 @@
* if (returnVal != 0)
* return returnVal;
* // Since the return codes in the public and external coder functions are identical
* // we offset error code by a fixed amount (1000) for differentiation.
* return (uint(1000) + this.coder_external(x_0, x_1));
* // we offset error code by a fixed amount (200000) for differentiation.
* returnVal = this.coder_external(x_0, x_1);
* if (returnVal != 0)
* return 200000 + returnVal;
* // Encode parameters
* bytes memory argumentEncoding = abi.encode(<parameter_names>);
* returnVal = checkEncodedCall(this.coder_public.selector, argumentEncoding, <invalidLengthFuzz>);
* // Check if calls to coder_public meet expectations for correctly/incorrectly encoded data.
* if (returnVal != 0)
* return returnVal;
*
* returnVal = checkEncodedCall(this.coder_external.selector, argumentEncoding, <invalidLengthFuzz>);
* // Check if calls to coder_external meet expectations for correctly/incorrectly encoded data.
* // Offset return value to distinguish between failures originating from coder_public and coder_external.
* if (returnVal != 0)
* return uint(200000) + returnVal;
* // Return zero if all checks pass.
* return 0;
* }
*
* // Utility functions
* /// Accepts function selector, correct argument encoding, and an invalid encoding length as input.
* /// Returns a non-zero value if either call with correct encoding fails or call with incorrect encoding
* /// succeeds. Returns zero if both calls meet expectation.
* function checkEncodedCall(bytes4 funcSelector, bytes memory argumentEncoding, uint invalidLengthFuzz)
* public returns (uint) {
* ...
* }
*
* /// Accepts function selector, correct argument encoding, and length of invalid encoding and returns
* /// the correct and incorrect abi encoding for calling the function specified by the function selector.
* function createEncoding(bytes4 funcSelector, bytes memory argumentEncoding, uint invalidLengthFuzz)
* internal pure returns (bytes memory, bytes memory) {
* ...
* }
*
* /// Compares two dynamically sized bytes arrays for equality.
* function bytesCompare(bytes memory a, bytes memory b) internal pure returns (bool) {
* ...
* }
@ -48,7 +79,7 @@
* // External function that is called by test() function. Accepts one or more arguments and returns
* // a uint value (zero if abi en/decoding was successful, non-zero otherwise)
* function coder_external(string calldata c_0, bytes calldata c_1) external pure returns (uint) {
* if (!stringCompare(c_0, "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d"))
* if (!bytesCompare(bytes(c_0), "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d"))
* return 1;
* if (!bytesCompare(c_1, "1"))
* return 2;
@ -375,7 +406,7 @@ private:
/// Monotonically increasing return value for error reporting
unsigned m_returnValue;
static unsigned constexpr s_maxArrayLength = 4;
static unsigned constexpr s_maxArrayDimensions = 10;
static unsigned constexpr s_maxArrayDimensions = 4;
/// Prefixes for declared and parameterized variable names
static auto constexpr s_varNamePrefix = "x_";
static auto constexpr s_paramNamePrefix = "c_";