diff --git a/test/tools/ossfuzz/protoToAbiV2.cpp b/test/tools/ossfuzz/protoToAbiV2.cpp index e44437662..4ef44b96b 100644 --- a/test/tools/ossfuzz/protoToAbiV2.cpp +++ b/test/tools/ossfuzz/protoToAbiV2.cpp @@ -204,12 +204,20 @@ std::string ProtoConverter::addressValueAsString(unsigned _counter) .render(); } -std::string ProtoConverter::fixedByteValueAsString(unsigned _width, unsigned _counter) +/// Returns a hex literal if _isHexLiteral is true, a string literal otherwise. +std::string ProtoConverter::hexValueAsString( + unsigned _width, + unsigned _counter, + bool _isHexLiteral +) { - solAssert( - (_width >= 1 && _width <= 32), - "Proto ABIv2 Fuzzer: Fixed byte width is not between 1--32" - ); + // If _width is zero (possible via a call from ...), then simply return an + // empty (hex) string + if (_width == 0) + return Whiskers(R"(hex"")") + ("addHexPrefix", _isHexLiteral) + .render(); + // Masked value must contain twice the number of nibble "f"'s as _width unsigned numMaskNibbles = _width * 2; // Start position of substring equals totalHexStringLength - numMaskNibbles @@ -225,11 +233,21 @@ std::string ProtoConverter::fixedByteValueAsString(unsigned _width, unsigned _co // and replaces "0x" with "hex\"...\"" string. // This is needed because solidity interprets a 20-byte 0x prefixed hex literal as an address // payable type. - return Whiskers(R"(hex"")") + return Whiskers(R"(hex"")") + ("addHexPrefix", _isHexLiteral) ("value", maskUnsignedIntToHex(_counter, numMaskNibbles).substr(startPos, numMaskNibbles)) .render(); } +std::string ProtoConverter::fixedByteValueAsString(unsigned _width, unsigned _counter) +{ + solAssert( + (_width >= 1 && _width <= 32), + "Proto ABIv2 Fuzzer: Fixed byte width is not between 1--32" + ); + return hexValueAsString(_width, _counter, /*isHexLiteral=*/true); +} + std::string ProtoConverter::integerValueAsString(bool _sign, unsigned _width, unsigned _counter) { if (_sign) @@ -314,10 +332,14 @@ void ProtoConverter::visit(ValueType const& _x) void ProtoConverter::visit(DynamicByteArrayType const& _x) { + bool isBytes = _x.type() == DynamicByteArrayType::BYTES; visitType( - (_x.type() == DynamicByteArrayType::BYTES) ? DataType::BYTES : DataType::STRING, + isBytes ? DataType::BYTES : DataType::STRING, bytesArrayTypeAsString(_x), - bytesArrayValueAsString(getNextCounter()) + bytesArrayValueAsString( + getNextCounter(), + isBytes + ) ); } @@ -367,7 +389,10 @@ std::string ProtoConverter::getValueByBaseType(ArrayType const& _x) case ArrayType::kBoolty: return boolValueAsString(getNextCounter()); case ArrayType::kDynbytesty: - return bytesArrayValueAsString(getNextCounter()); + return bytesArrayValueAsString( + getNextCounter(), + _x.dynbytesty().type() == DynamicByteArrayType::BYTES + ); // TODO: Implement structs. case ArrayType::kStty: case ArrayType::BASE_TYPE_ONEOF_NOT_SET: @@ -695,6 +720,7 @@ void ProtoConverter::visit(TestFunction const& _x) if (returnVal != 0) return uint(200000) + returnVal; + bytes memory argumentEncoding = abi.encode(); returnVal = checkEncodedCall(this.coder_public.selector, argumentEncoding, ); @@ -704,11 +730,13 @@ void ProtoConverter::visit(TestFunction const& _x) returnVal = checkEncodedCall(this.coder_external.selector, argumentEncoding, ); 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())) + ("atLeastOneVar", m_varCounter > 0) .render(); } @@ -730,8 +758,12 @@ void ProtoConverter::writeHelperFunctions() 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; + // Ensure that invalidEncoding crops at least 32 bytes (padding length + // is at most 31 bytes) since shorter bytes/string values can lead to + // successful decoding when fewer than 32 bytes have been cropped in + // the worst case. In other words, + // 0 <= invalidLength <= argumentEncoding.length - 32 + uint invalidLength = invalidLengthFuzz % (argumentEncoding.length - 31); bytes memory invalidEncoding = new bytes(4 + invalidLength); for (uint i = 0; i < 4; i++) validEncoding[i] = invalidEncoding[i] = funcSelector[i]; diff --git a/test/tools/ossfuzz/protoToAbiV2.h b/test/tools/ossfuzz/protoToAbiV2.h index f302c0b74..83c1f60c1 100644 --- a/test/tools/ossfuzz/protoToAbiV2.h +++ b/test/tools/ossfuzz/protoToAbiV2.h @@ -268,13 +268,6 @@ private: ); } - // String and bytes literals are derived by hashing a monotonically increasing - // counter and enclosing the said hash inside double quotes. - std::string bytesArrayValueAsString(unsigned _counter) - { - return "\"" + toHex(hashUnsignedInt(_counter), HexPrefix::DontAdd) + "\""; - } - std::string getQualifier(DataType _dataType) { return ((isValueType(_dataType) || m_isStateVar) ? "" : "memory"); @@ -288,6 +281,11 @@ private: static std::string integerValueAsString(bool _sign, unsigned _width, unsigned _counter); static std::string addressValueAsString(unsigned _counter); static std::string fixedByteValueAsString(unsigned _width, unsigned _counter); + static std::string hexValueAsString( + unsigned _width, + unsigned _counter, + bool _isHexLiteral + ); static std::vector> arrayDimensionsAsPairVector(ArrayType const& _x); static std::string arrayDimInfoAsString(ArrayDimensionInfo const& _x); static void arrayDimensionsAsStringVector( @@ -389,6 +387,19 @@ private: ); } + // String and bytes literals are derived by hashing a monotonically increasing + // counter and enclosing the (potentially cropped) hash inside double quotes. + // Cropping is achieved by masking out higher order bits. + // TODO: Test invalid encoding of bytes/string arguments that hold values of over 32 bytes. + // See https://github.com/ethereum/solidity/issues/7180 + static std::string bytesArrayValueAsString(unsigned _counter, bool _isHexLiteral) + { + // We use _counter to not only create a value but to crop it + // to a length (l) such that 0 <= l <= 32 (hence the use of 33 as + // the modulo constant) + return hexValueAsString(_counter % 33, _counter, _isHexLiteral); + } + /// Contains the test program std::ostringstream m_output; /// Temporary storage for state variable definitions