Fail on invalid storage encoding for byte arrays.

This commit is contained in:
chriseth
2020-11-12 11:12:42 +01:00
parent 14ed67ac4b
commit d744a8fb48
7 changed files with 150 additions and 68 deletions
+3 -4
View File
@@ -683,18 +683,16 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
// <readableTypeNameFrom> -> <readableTypeNameTo>
function <functionName>(value, pos) -> ret {
let slotValue := sload(value)
let length := <byteArrayLengthFunction>(slotValue)
pos := <storeLength>(pos, length)
switch and(slotValue, 1)
case 0 {
// short byte array
let length := and(div(slotValue, 2), 0x7f)
pos := <storeLength>(pos, length)
mstore(pos, and(slotValue, not(0xff)))
ret := add(pos, <lengthPaddedShort>)
}
case 1 {
// long byte array
let length := div(slotValue, 2)
pos := <storeLength>(pos, length)
let dataPos := <arrayDataSlot>(value)
let i := 0
for { } lt(i, length) { i := add(i, 0x20) } {
@@ -708,6 +706,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
templ("functionName", functionName);
templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true));
templ("byteArrayLengthFunction", m_utils.extractByteArrayLengthFunction());
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
templ("lengthPaddedShort", _options.padded ? "0x20" : "length");
templ("lengthPaddedLong", _options.padded ? "i" : "length");
+24 -41
View File
@@ -818,30 +818,25 @@ void ArrayUtils::incrementDynamicArraySize(ArrayType const& _type) const
// lowest-order byte (we actually use a mask with fewer bits) must
// be (31*2+0) = 62
m_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::DUP1;
m_context.callYulFunction(m_context.utilFunctions().extractByteArrayLengthFunction(), 1, 1);
m_context.appendInlineAssembly(R"({
let data := sload(ref)
let shifted_length := and(data, 63)
// We have to copy if length is exactly 31, because that marks
// the transition between in-place and out-of-place storage.
switch shifted_length
case 62
switch length
case 31
{
mstore(0, ref)
let data_area := keccak256(0, 0x20)
sstore(data_area, and(data, not(0xff)))
// New length is 32, encoded as (32 * 2 + 1)
sstore(ref, 65)
// Replace ref variable by new length
ref := 32
// Set old length in new format (31 * 2 + 1)
data := 63
}
default
{
sstore(ref, add(data, 2))
// Replace ref variable by new length
if iszero(and(data, 1)) { data := shifted_length }
ref := add(div(data, 2), 1)
}
})", {"ref"});
sstore(ref, add(data, 2))
// return new length in ref
ref := add(length, 1)
})", {"ref", "data", "length"});
m_context << Instruction::POP << Instruction::POP;
}
else
m_context.appendInlineAssembly(R"({
@@ -860,28 +855,25 @@ void ArrayUtils::popStorageArrayElement(ArrayType const& _type) const
if (_type.isByteArray())
{
m_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::DUP1;
m_context.callYulFunction(m_context.utilFunctions().extractByteArrayLengthFunction(), 1, 1);
util::Whiskers code(R"({
let slot_value := sload(ref)
switch and(slot_value, 1)
if iszero(length) {
mstore(0, <panicSelector>)
mstore(4, <emptyArrayPop>)
revert(0, 0x24)
}
switch gt(length, 31)
case 0 {
// short byte array
let length := and(div(slot_value, 2), 0x1f)
if iszero(length) {
mstore(0, <panicSelector>)
mstore(4, <emptyArrayPop>)
revert(0, 0x24)
}
// Zero-out the suffix including the least significant byte.
let mask := sub(exp(0x100, sub(33, length)), 1)
length := sub(length, 1)
slot_value := or(and(not(mask), slot_value), mul(length, 2))
sstore(ref, slot_value)
}
case 1 {
// long byte array
mstore(0, ref)
let length := div(slot_value, 2)
let slot := keccak256(0, 0x20)
switch length
case 32
@@ -889,7 +881,7 @@ void ArrayUtils::popStorageArrayElement(ArrayType const& _type) const
let data := sload(slot)
sstore(slot, 0)
data := and(data, not(0xff))
sstore(ref, or(data, 62))
slot_value := or(data, 62)
}
default
{
@@ -905,14 +897,14 @@ void ArrayUtils::popStorageArrayElement(ArrayType const& _type) const
// Reduce the length by 1
slot_value := sub(slot_value, 2)
sstore(ref, slot_value)
}
}
sstore(ref, slot_value)
})");
code("panicSelector", util::selectorFromSignature("Panic(uint256)").str());
code("emptyArrayPop", to_string(unsigned(util::PanicCode::EmptyArrayPop)));
m_context.appendInlineAssembly(code.render(), {"ref"});
m_context << Instruction::POP;
m_context.appendInlineAssembly(code.render(), {"ref", "slot_value", "length"});
m_context << Instruction::POP << Instruction::POP << Instruction::POP;
}
else
{
@@ -1040,16 +1032,7 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType, unsigned _stackDept
case DataLocation::Storage:
m_context << Instruction::SLOAD;
if (_arrayType.isByteArray())
{
// Retrieve length both for in-place strings and off-place strings:
// Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2
// i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it
// computes (x & (-1)) / 2, which is equivalent to just x / 2.
m_context << u256(1) << Instruction::DUP2 << u256(1) << Instruction::AND;
m_context << Instruction::ISZERO << u256(0x100) << Instruction::MUL;
m_context << Instruction::SUB << Instruction::AND;
m_context << u256(2) << Instruction::SWAP1 << Instruction::DIV;
}
m_context.callYulFunction(m_context.utilFunctions().extractByteArrayLengthFunction(), 1, 1);
break;
}
}
+23 -19
View File
@@ -1003,25 +1003,6 @@ string YulUtilFunctions::wrappingIntExpFunction(
});
}
string YulUtilFunctions::extractByteArrayLengthFunction()
{
string functionName = "extract_byte_array_length";
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"(
function <functionName>(data) -> length {
// Retrieve length both for in-place strings and off-place strings:
// Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2
// i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it
// computes (x & (-1)) / 2, which is equivalent to just x / 2.
let mask := sub(mul(0x100, iszero(and(data, 1))), 1)
length := div(and(data, mask), 2)
}
)");
w("functionName", functionName);
return w.render();
});
}
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
{
string functionName = "array_length_" + _type.identifier();
@@ -1064,6 +1045,29 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
});
}
string YulUtilFunctions::extractByteArrayLengthFunction()
{
string functionName = "extract_byte_array_length";
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"(
function <functionName>(data) -> length {
length := div(data, 2)
let outOfPlaceEncoding := and(data, 1)
if iszero(outOfPlaceEncoding) {
length := and(length, 0x7f)
}
if eq(outOfPlaceEncoding, lt(length, 32)) {
<panic>()
}
}
)");
w("functionName", functionName);
w("panic", panicFunction(PanicCode::StorageEncodingError));
return w.render();
});
}
std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
+6 -4
View File
@@ -178,6 +178,12 @@ public:
/// signature: (array) -> length
std::string arrayLengthFunction(ArrayType const& _type);
/// @returns function name that extracts and returns byte array length from the value
/// stored at the slot.
/// Causes a Panic if the length encoding is wrong.
/// signature: (data) -> length
std::string extractByteArrayLengthFunction();
/// @returns the name of a function that resizes a storage array
/// signature: (array, newLen)
std::string resizeDynamicArrayFunction(ArrayType const& _type);
@@ -447,10 +453,6 @@ private:
/// use exactly one variable to hold the value.
std::string conversionFunctionSpecial(Type const& _from, Type const& _to);
/// @returns function name that extracts and returns byte array length
/// signature: (data) -> length
std::string extractByteArrayLengthFunction();
/// @returns the name of a function that reduces the size of a storage byte array by one element
/// signature: (byteArray)
std::string storageByteArrayPopFunction(ArrayType const& _type);