Validate value types in decoder of ABIEncoderV2.

This commit is contained in:
Alex Beregszaszi 2019-01-17 22:08:05 +00:00 committed by chriseth
parent e9ffbefb05
commit 43008dd08e
5 changed files with 135 additions and 55 deletions

View File

@ -5,6 +5,7 @@ Language Features:
Compiler Features: Compiler Features:
* ABI Decoder: Raise a runtime error on dirty inputs when using the experimental decoder.
* SMTChecker: Support arithmetic compound assignment operators. * SMTChecker: Support arithmetic compound assignment operators.
* Optimizer: Add rule for shifts by constants larger than 255 for Constantinople. * Optimizer: Add rule for shifts by constants larger than 255 for Constantinople.
* Optimizer: Add rule to simplify certain ANDs and SHL combinations * Optimizer: Add rule to simplify certain ANDs and SHL combinations

View File

@ -255,10 +255,9 @@ string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
return suffix; return suffix;
} }
string ABIFunctions::cleanupFunction(Type const& _type)
string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
{ {
string functionName = string("cleanup_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); string functionName = string("cleanup_") + _type.identifier();
return createFunction(functionName, [&]() { return createFunction(functionName, [&]() {
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(value) -> cleaned { function <functionName>(value) -> cleaned {
@ -269,7 +268,7 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
switch (_type.category()) switch (_type.category())
{ {
case Type::Category::Address: case Type::Category::Address:
templ("body", "cleaned := " + cleanupFunction(IntegerType(160), _revertOnFailure) + "(value)"); templ("body", "cleaned := " + cleanupFunction(IntegerType(160)) + "(value)");
break; break;
case Type::Category::Integer: case Type::Category::Integer:
{ {
@ -291,6 +290,10 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
case Type::Category::FixedPoint: case Type::Category::FixedPoint:
solUnimplemented("Fixed point types not implemented."); solUnimplemented("Fixed point types not implemented.");
break; break;
case Type::Category::Function:
solAssert(dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External, "");
templ("body", "cleaned := " + cleanupFunction(FixedBytesType(24)) + "(value)");
break;
case Type::Category::Array: case Type::Category::Array:
case Type::Category::Struct: case Type::Category::Struct:
case Type::Category::Mapping: case Type::Category::Mapping:
@ -319,20 +322,13 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
StateMutability::Payable : StateMutability::Payable :
StateMutability::NonPayable StateMutability::NonPayable
); );
templ("body", "cleaned := " + cleanupFunction(addressType, _revertOnFailure) + "(value)"); templ("body", "cleaned := " + cleanupFunction(addressType) + "(value)");
break; break;
} }
case Type::Category::Enum: case Type::Category::Enum:
{ {
size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers(); // Out of range enums cannot be truncated unambigiously and therefore it should be an error.
solAssert(members > 0, "empty enum should have caused a parser error."); templ("body", "cleaned := value " + validatorFunction(_type) + "(value)");
Whiskers w("if iszero(lt(value, <members>)) { <failure> } cleaned := value");
w("members", to_string(members));
if (_revertOnFailure)
w("failure", "revert(0, 0)");
else
w("failure", "invalid()");
templ("body", w.render());
break; break;
} }
case Type::Category::InaccessibleDynamic: case Type::Category::InaccessibleDynamic:
@ -346,6 +342,56 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
}); });
} }
string ABIFunctions::validatorFunction(Type const& _type, bool _revertOnFailure)
{
string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
return createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(value) {
if iszero(<condition>) { <failure> }
}
)");
templ("functionName", functionName);
if (_revertOnFailure)
templ("failure", "revert(0, 0)");
else
templ("failure", "invalid()");
switch (_type.category())
{
case Type::Category::Address:
case Type::Category::Integer:
case Type::Category::RationalNumber:
case Type::Category::Bool:
case Type::Category::FixedPoint:
case Type::Category::Function:
case Type::Category::Array:
case Type::Category::Struct:
case Type::Category::Mapping:
case Type::Category::FixedBytes:
case Type::Category::Contract:
{
templ("condition", "eq(value, " + cleanupFunction(_type) + "(value))");
break;
}
case Type::Category::Enum:
{
size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers();
solAssert(members > 0, "empty enum should have caused a parser error.");
templ("condition", "lt(value, " + to_string(members) + ")");
break;
}
case Type::Category::InaccessibleDynamic:
templ("condition", "1");
break;
default:
solAssert(false, "Validation of type " + _type.identifier() + " requested.");
}
return templ.render();
});
}
string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes) string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes)
{ {
solAssert(_type.isValueType(), ""); solAssert(_type.isValueType(), "");
@ -544,21 +590,6 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
}); });
} }
string ABIFunctions::cleanupCombinedExternalFunctionIdFunction()
{
string functionName = "cleanup_combined_external_function_id";
return createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(addr_and_selector) -> cleaned {
cleaned := <clean>(addr_and_selector)
}
)")
("functionName", functionName)
("clean", cleanupFunction(FixedBytesType(24)))
.render();
});
}
string ABIFunctions::abiEncodingFunction( string ABIFunctions::abiEncodingFunction(
Type const& _from, Type const& _from,
Type const& _to, Type const& _to,
@ -1248,7 +1279,7 @@ string ABIFunctions::abiEncodingFunctionFunctionType(
} }
)") )")
("functionName", functionName) ("functionName", functionName)
("cleanExtFun", cleanupCombinedExternalFunctionIdFunction()) ("cleanExtFun", cleanupFunction(_to))
.render(); .render();
}); });
} }
@ -1306,14 +1337,15 @@ string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromM
return createFunction(functionName, [&]() { return createFunction(functionName, [&]() {
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(offset, end) -> value { function <functionName>(offset, end) -> value {
value := <cleanup>(<load>(offset)) value := <load>(offset)
<validator>(value)
} }
)"); )");
templ("functionName", functionName); templ("functionName", functionName);
templ("load", _fromMemory ? "mload" : "calldataload"); templ("load", _fromMemory ? "mload" : "calldataload");
// Cleanup itself should use the type and not decodingType, because e.g. // Validation should use the type and not decodingType, because e.g.
// the decoding type of an enum is a plain int. // the decoding type of an enum is a plain int.
templ("cleanup", cleanupFunction(_type, true)); templ("validator", validatorFunction(_type, true));
return templ.render(); return templ.render();
}); });
@ -1560,11 +1592,11 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
{ {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(offset, end) -> addr, function_selector { function <functionName>(offset, end) -> addr, function_selector {
addr, function_selector := <splitExtFun>(<load>(offset)) addr, function_selector := <splitExtFun>(<decodeFun>(offset, end))
} }
)") )")
("functionName", functionName) ("functionName", functionName)
("load", _fromMemory ? "mload" : "calldataload") ("decodeFun", abiDecodingFunctionFunctionType(_type, _fromMemory, false))
("splitExtFun", m_utils.splitExternalFunctionIdFunction()) ("splitExtFun", m_utils.splitExternalFunctionIdFunction())
.render(); .render();
} }
@ -1572,12 +1604,13 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
{ {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(offset, end) -> fun { function <functionName>(offset, end) -> fun {
fun := <cleanExtFun>(<load>(offset)) fun := <load>(offset)
<validateExtFun>(fun)
} }
)") )")
("functionName", functionName) ("functionName", functionName)
("load", _fromMemory ? "mload" : "calldataload") ("load", _fromMemory ? "mload" : "calldataload")
("cleanExtFun", cleanupCombinedExternalFunctionIdFunction()) ("validateExtFun", validatorFunction(_type, true))
.render(); .render();
} }
}); });

View File

@ -130,9 +130,17 @@ private:
/// @returns the name of the cleanup function for the given type and /// @returns the name of the cleanup function for the given type and
/// adds its implementation to the requested functions. /// adds its implementation to the requested functions.
/// The cleanup function defers to the validator function with "assert"
/// if there is no reasonable way to clean a value.
std::string cleanupFunction(Type const& _type);
/// @returns the name of the validator function for the given type and
/// adds its implementation to the requested functions.
/// @param _revertOnFailure if true, causes revert on invalid data, /// @param _revertOnFailure if true, causes revert on invalid data,
/// otherwise an assertion failure. /// otherwise an assertion failure.
std::string cleanupFunction(Type const& _type, bool _revertOnFailure = false); ///
/// This is used for data decoded from external sources.
std::string validatorFunction(Type const& _type, bool _revertOnFailure = false);
/// Performs cleanup after reading from a potentially compressed storage slot. /// Performs cleanup after reading from a potentially compressed storage slot.
/// The function does not perform any validation, it just masks or sign-extends /// The function does not perform any validation, it just masks or sign-extends
@ -146,10 +154,10 @@ private:
/// @returns the name of the function that converts a value of type @a _from /// @returns the name of the function that converts a value of type @a _from
/// to a value of type @a _to. The resulting vale is guaranteed to be in range /// to a value of type @a _to. The resulting vale is guaranteed to be in range
/// (i.e. "clean"). Asserts on failure. /// (i.e. "clean"). Asserts on failure.
///
/// This is used for data being encoded or general type conversions in the code.
std::string conversionFunction(Type const& _from, Type const& _to); std::string conversionFunction(Type const& _from, Type const& _to);
std::string cleanupCombinedExternalFunctionIdFunction();
/// @returns the name of the ABI encoding function with the given type /// @returns the name of the ABI encoding function with the given type
/// and queues the generation of the function to the requested functions. /// and queues the generation of the function to the requested functions.
/// @param _fromStack if false, the input value was just loaded from storage /// @param _fromStack if false, the input value was just loaded from storage

View File

@ -1406,6 +1406,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
case Type::Category::Struct: case Type::Category::Struct:
{ {
StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.expression().annotation().type); StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.expression().annotation().type);
TypePointer const& memberType = _memberAccess.annotation().type;
switch (type.location()) switch (type.location())
{ {
case DataLocation::Storage: case DataLocation::Storage:
@ -1418,7 +1419,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
case DataLocation::Memory: case DataLocation::Memory:
{ {
m_context << type.memoryOffsetOfMember(member) << Instruction::ADD; m_context << type.memoryOffsetOfMember(member) << Instruction::ADD;
setLValue<MemoryItem>(_memberAccess, *_memberAccess.annotation().type); setLValue<MemoryItem>(_memberAccess, *memberType);
break; break;
} }
case DataLocation::CallData: case DataLocation::CallData:
@ -1427,21 +1428,28 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
{ {
m_context << Instruction::DUP1; m_context << Instruction::DUP1;
m_context << type.calldataOffsetOfMember(member) << Instruction::ADD; m_context << type.calldataOffsetOfMember(member) << Instruction::ADD;
CompilerUtils(m_context).accessCalldataTail(*_memberAccess.annotation().type); CompilerUtils(m_context).accessCalldataTail(*memberType);
} }
else else
{ {
m_context << type.calldataOffsetOfMember(member) << Instruction::ADD; m_context << type.calldataOffsetOfMember(member) << Instruction::ADD;
// For non-value types the calldata offset is returned directly. // For non-value types the calldata offset is returned directly.
if (_memberAccess.annotation().type->isValueType()) if (memberType->isValueType())
{ {
solAssert(_memberAccess.annotation().type->calldataEncodedSize() > 0, ""); solAssert(memberType->calldataEncodedSize() > 0, "");
CompilerUtils(m_context).loadFromMemoryDynamic(*_memberAccess.annotation().type, true, true, false); solAssert(memberType->storageBytes() <= 32, "");
if (memberType->storageBytes() < 32 && m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
{
m_context << u256(32);
CompilerUtils(m_context).abiDecodeV2({memberType}, false);
}
else
CompilerUtils(m_context).loadFromMemoryDynamic(*memberType, true, true, false);
} }
else else
solAssert( solAssert(
_memberAccess.annotation().type->category() == Type::Category::Array || memberType->category() == Type::Category::Array ||
_memberAccess.annotation().type->category() == Type::Category::Struct, memberType->category() == Type::Category::Struct,
"" ""
); );
} }
@ -1588,12 +1596,25 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
{ {
ArrayUtils(m_context).accessIndex(arrayType, true); ArrayUtils(m_context).accessIndex(arrayType, true);
if (arrayType.baseType()->isValueType()) if (arrayType.baseType()->isValueType())
CompilerUtils(m_context).loadFromMemoryDynamic( {
*arrayType.baseType(), solAssert(arrayType.baseType()->storageBytes() <= 32, "");
true, if (
!arrayType.isByteArray(), !arrayType.isByteArray() &&
false arrayType.baseType()->storageBytes() < 32 &&
); m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)
)
{
m_context << u256(32);
CompilerUtils(m_context).abiDecodeV2({arrayType.baseType()}, false);
}
else
CompilerUtils(m_context).loadFromMemoryDynamic(
*arrayType.baseType(),
true,
!arrayType.isByteArray(),
false
);
}
else else
solAssert( solAssert(
arrayType.baseType()->category() == Type::Category::Struct || arrayType.baseType()->category() == Type::Category::Struct ||

View File

@ -117,7 +117,7 @@ BOOST_AUTO_TEST_CASE(cleanup)
ABI_CHECK( ABI_CHECK(
callContractFunction( callContractFunction(
"f(uint16,int16,address,bytes3,bool)", "f(uint16,int16,address,bytes3,bool)",
u256(0xffffff), u256(0x1ffff), u256(-1), string("abcd"), u256(4) u256(0xffffff), u256(0x1ffff), u256(-1), string("abcd"), u256(1)
), ),
encodeArgs(u256(0xffff), u256(-1), (u256(1) << 160) - 1, string("abc"), true) encodeArgs(u256(0xffff), u256(-1), (u256(1) << 160) - 1, string("abc"), true)
); );
@ -759,7 +759,6 @@ BOOST_AUTO_TEST_CASE(complex_struct)
) )
} }
BOOST_AUTO_TEST_CASE(return_dynamic_types_cross_call_simple) BOOST_AUTO_TEST_CASE(return_dynamic_types_cross_call_simple)
{ {
if (m_evmVersion == langutil::EVMVersion::homestead()) if (m_evmVersion == langutil::EVMVersion::homestead())
@ -844,6 +843,24 @@ BOOST_AUTO_TEST_CASE(return_dynamic_types_cross_call_out_of_range)
) )
} }
BOOST_AUTO_TEST_CASE(out_of_bounds_bool_value)
{
string sourceCode = R"(
contract C {
function f(bool b) public pure returns (bool) { return b; }
}
)";
bool newDecoder = false;
BOTH_ENCODERS(
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("f(bool)", true), encodeArgs(true));
ABI_CHECK(callContractFunction("f(bool)", false), encodeArgs(false));
ABI_CHECK(callContractFunctionNoEncoding("f(bool)", bytes(32, 0)), encodeArgs(0));
ABI_CHECK(callContractFunctionNoEncoding("f(bool)", bytes(32, 0xff)), newDecoder ? encodeArgs() : encodeArgs(1));
newDecoder = true;
)
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()
} }