Adds boundary checks when copying dynamic fields (includes tests)

This commit is contained in:
Greg Hysen
2019-06-04 12:37:03 -07:00
parent 899d0f1e42
commit 1212e534a8
3 changed files with 342 additions and 5 deletions

View File

@@ -163,6 +163,10 @@ contract ERC1155Proxy is
// Load length in bytes of `assetData`
let assetDataLength := calldataload(assetDataOffset)
// End of asset data in calldata
// +32 for length field
let assetDataEnd := add(assetDataOffset, add(assetDataLength, 32))
// Load offset to parameters section in asset data
let paramsInAssetDataOffset := add(assetDataOffset, 36)
@@ -181,6 +185,16 @@ contract ERC1155Proxy is
let idsOffset := add(paramsInAssetDataOffset, calldataload(add(assetDataOffset, 68)))
let idsLength := calldataload(idsOffset)
let idsLengthInBytes := mul(idsLength, 32)
let idsBegin := add(idsOffset, 32)
let idsEnd := add(idsBegin, idsLengthInBytes)
if gt(idsEnd, assetDataEnd) {
// Revert with `Error("INVALID_IDS_OFFSET")`
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
mstore(64, 0x00000012494e56414c49445f4944535f4f464653455400000000000000000000)
mstore(96, 0)
revert(0, 100)
}
calldatacopy(
dataAreaEndOffset,
idsOffset,
@@ -196,14 +210,22 @@ contract ERC1155Proxy is
let valuesOffset := add(paramsInAssetDataOffset, calldataload(add(assetDataOffset, 100)))
let valuesLength := calldataload(valuesOffset)
let valuesLengthInBytes := mul(valuesLength, 32)
let valuesBegin := add(valuesOffset, 32)
let valuesEnd := add(valuesBegin, valuesLengthInBytes)
if gt(valuesEnd, assetDataEnd) {
// Revert with `Error("INVALID_VALUES_OFFSET")`
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
mstore(64, 0x00000015494e56414c49445f56414c5545535f4f464653455400000000000000)
mstore(96, 0)
revert(0, 100)
}
// Store length of `values`
mstore(dataAreaEndOffset, valuesLength)
dataAreaEndOffset := add(dataAreaEndOffset, 32)
// Scale and store elements of `values`
let valuesBegin := add(valuesOffset, 32)
let valuesEnd := add(valuesBegin, valuesLengthInBytes)
for { let currentValueOffset := valuesBegin }
lt(currentValueOffset, valuesEnd)
{ currentValueOffset := add(currentValueOffset, 32) }
@@ -237,13 +259,25 @@ contract ERC1155Proxy is
// Copy `data` from `assetData` (Table #2) to memory (Table #3)
let dataOffset := add(paramsInAssetDataOffset, calldataload(add(assetDataOffset, 132)))
let dataLengthInBytes := calldataload(dataOffset)
let dataLengthInWords := div(add(dataLengthInBytes, 31), 32)
let dataLengthInBytesWordAligned := mul(dataLengthInWords, 32)
let dataBegin := add(dataOffset, 32)
let dataEnd := add(dataBegin, dataLengthInBytes)
if gt(dataEnd, assetDataEnd) {
// Revert with `Error("INVALID_DATA_OFFSET")`
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
mstore(64, 0x00000013494e56414c49445f444154415f4f4646534554000000000000000000)
mstore(96, 0)
revert(0, 100)
}
calldatacopy(
dataAreaEndOffset,
dataOffset,
add(dataLengthInBytesWordAligned, 32)
add(dataLengthInBytes, 32)
)
// Update the end of data offset to be word-aligned
let dataLengthInWords := div(add(dataLengthInBytes, 31), 32)
let dataLengthInBytesWordAligned := mul(dataLengthInWords, 32)
dataAreaEndOffset := add(dataAreaEndOffset, add(dataLengthInBytesWordAligned, 32))
////////// STEP 3/3 - Execute Transfer //////////

View File

@@ -1052,6 +1052,306 @@ describe('ERC1155Proxy', () => {
];
expect(finalBalances).to.be.deep.equal(expectedFinalBalances);
});
it('should revert if token ids resolves to outside the bounds of calldata', async () => {
// setup test parameters
const tokensToTransfer = fungibleTokens.slice(0, 1);
const valuesToTransfer = [fungibleValueToTransferLarge];
const valueMultiplier = valueMultiplierSmall;
const erc1155ContractAddress = erc1155Wrapper.getContract().address;
const assetData = assetDataUtils.encodeERC1155AssetData(
erc1155ContractAddress,
tokensToTransfer,
valuesToTransfer,
receiverCallbackData,
);
// The asset data we just generated will look like this:
// a7cb5fb7
// 0x 0000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e8082
// 0x20 0000000000000000000000000000000000000000000000000000000000000080 // offset to token ids
// 0x40 00000000000000000000000000000000000000000000000000000000000000c0
// 0x60 0000000000000000000000000000000000000000000000000000000000000100
// 0x80 0000000000000000000000000000000000000000000000000000000000000001
// 0xA0 0000000000000000000000000000000100000000000000000000000000000000
// 0xC0 0000000000000000000000000000000000000000000000000000000000000001
// 0xE0 0000000000000000000000000000000000000000000000878678326eac900000
// 0x100 0000000000000000000000000000000000000000000000000000000000000004
// 0x120 0102030400000000000000000000000000000000000000000000000000000000
//
// We want to chan ge the offset to token ids to point outside the calldata.
const encodedOffsetToTokenIds = '0000000000000000000000000000000000000000000000000000000000000080';
const badEncodedOffsetToTokenIds = '0000000000000000000000000000000000000000000000000000000000000180';
const assetDataWithBadTokenIdsOffset = assetData.replace(
encodedOffsetToTokenIds,
badEncodedOffsetToTokenIds,
);
// execute transfer
await expectTransactionFailedAsync(
erc1155ProxyWrapper.transferFromAsync(
spender,
receiverContract,
erc1155Contract.address,
tokensToTransfer,
valuesToTransfer,
valueMultiplier,
receiverCallbackData,
authorized,
assetDataWithBadTokenIdsOffset,
),
RevertReason.InvalidIdsOffset,
);
});
it('should revert if an element of token ids lies to outside the bounds of calldata', async () => {
// setup test parameters
const tokensToTransfer = fungibleTokens.slice(0, 1);
const valuesToTransfer = [fungibleValueToTransferLarge];
const valueMultiplier = valueMultiplierSmall;
const erc1155ContractAddress = erc1155Wrapper.getContract().address;
const assetData = assetDataUtils.encodeERC1155AssetData(
erc1155ContractAddress,
tokensToTransfer,
valuesToTransfer,
receiverCallbackData,
);
// The asset data we just generated will look like this:
// a7cb5fb7
// 0x 0000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e8082
// 0x20 0000000000000000000000000000000000000000000000000000000000000080 // offset to token ids
// 0x40 00000000000000000000000000000000000000000000000000000000000000c0
// 0x60 0000000000000000000000000000000000000000000000000000000000000100
// 0x80 0000000000000000000000000000000000000000000000000000000000000001
// 0xA0 0000000000000000000000000000000100000000000000000000000000000000
// 0xC0 0000000000000000000000000000000000000000000000000000000000000001
// 0xE0 0000000000000000000000000000000000000000000000878678326eac900000
// 0x100 0000000000000000000000000000000000000000000000000000000000000004
// 0x120 0102030400000000000000000000000000000000000000000000000000000000
//
// We want to chan ge the offset to token ids to the end of calldata.
// Then we'll add an invalid length: we encode length of 2 but only add 1 element.
const encodedOffsetToTokenIds = '0000000000000000000000000000000000000000000000000000000000000080';
const newEcodedOffsetToTokenIds = '0000000000000000000000000000000000000000000000000000000000000140';
const assetDataWithNewTokenIdsOffset = assetData.replace(
encodedOffsetToTokenIds,
newEcodedOffsetToTokenIds,
);
const encodedTokenIdsLength = '0000000000000000000000000000000000000000000000000000000000000002';
const encodedTokenIdValues = '0000000000000000000000000000000000000000000000000000000000000001';
const assetDataWithBadTokenIds = `${assetDataWithNewTokenIdsOffset}${encodedTokenIdsLength}${encodedTokenIdValues}`;
// execute transfer
await expectTransactionFailedAsync(
erc1155ProxyWrapper.transferFromAsync(
spender,
receiverContract,
erc1155Contract.address,
tokensToTransfer,
valuesToTransfer,
valueMultiplier,
receiverCallbackData,
authorized,
assetDataWithBadTokenIds,
),
RevertReason.InvalidIdsOffset,
);
});
it('should revert if token values resolves to outside the bounds of calldata', async () => {
// setup test parameters
const tokensToTransfer = fungibleTokens.slice(0, 1);
const valuesToTransfer = [fungibleValueToTransferLarge];
const valueMultiplier = valueMultiplierSmall;
const erc1155ContractAddress = erc1155Wrapper.getContract().address;
const assetData = assetDataUtils.encodeERC1155AssetData(
erc1155ContractAddress,
tokensToTransfer,
valuesToTransfer,
receiverCallbackData,
);
// The asset data we just generated will look like this:
// a7cb5fb7
// 0x 0000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e8082
// 0x20 0000000000000000000000000000000000000000000000000000000000000080
// 0x40 00000000000000000000000000000000000000000000000000000000000000c0 // offset to token values
// 0x60 0000000000000000000000000000000000000000000000000000000000000100
// 0x80 0000000000000000000000000000000000000000000000000000000000000001
// 0xA0 0000000000000000000000000000000100000000000000000000000000000000
// 0xC0 0000000000000000000000000000000000000000000000000000000000000001
// 0xE0 0000000000000000000000000000000000000000000000878678326eac900000
// 0x100 0000000000000000000000000000000000000000000000000000000000000004
// 0x120 0102030400000000000000000000000000000000000000000000000000000000
//
// We want to chan ge the offset to token values to point outside the calldata.
const encodedOffsetToTokenValues = '00000000000000000000000000000000000000000000000000000000000000c0';
const badEncodedOffsetToTokenValues = '00000000000000000000000000000000000000000000000000000000000001c0';
const assetDataWithBadTokenIdsOffset = assetData.replace(
encodedOffsetToTokenValues,
badEncodedOffsetToTokenValues,
);
// execute transfer
await expectTransactionFailedAsync(
erc1155ProxyWrapper.transferFromAsync(
spender,
receiverContract,
erc1155Contract.address,
tokensToTransfer,
valuesToTransfer,
valueMultiplier,
receiverCallbackData,
authorized,
assetDataWithBadTokenIdsOffset,
),
RevertReason.InvalidValuesOffset,
);
});
it('should revert if an element of token values lies to outside the bounds of calldata', async () => {
// setup test parameters
const tokensToTransfer = fungibleTokens.slice(0, 1);
const valuesToTransfer = [fungibleValueToTransferLarge];
const valueMultiplier = valueMultiplierSmall;
const erc1155ContractAddress = erc1155Wrapper.getContract().address;
const assetData = assetDataUtils.encodeERC1155AssetData(
erc1155ContractAddress,
tokensToTransfer,
valuesToTransfer,
receiverCallbackData,
);
// The asset data we just generated will look like this:
// a7cb5fb7
// 0x 0000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e8082
// 0x20 0000000000000000000000000000000000000000000000000000000000000080
// 0x40 00000000000000000000000000000000000000000000000000000000000000c0 // offset to token values
// 0x60 0000000000000000000000000000000000000000000000000000000000000100
// 0x80 0000000000000000000000000000000000000000000000000000000000000001
// 0xA0 0000000000000000000000000000000100000000000000000000000000000000
// 0xC0 0000000000000000000000000000000000000000000000000000000000000001
// 0xE0 0000000000000000000000000000000000000000000000878678326eac900000
// 0x100 0000000000000000000000000000000000000000000000000000000000000004
// 0x120 0102030400000000000000000000000000000000000000000000000000000000
//
// We want to chan ge the offset to token values to the end of calldata.
// Then we'll add an invalid length: we encode length of 2 but only add 1 element.
const encodedOffsetToTokenValues = '00000000000000000000000000000000000000000000000000000000000000c0';
const newEcodedOffsetToTokenValues = '0000000000000000000000000000000000000000000000000000000000000140';
const assetDataWithNewTokenValuesOffset = assetData.replace(
encodedOffsetToTokenValues,
newEcodedOffsetToTokenValues,
);
const encodedTokenValuesLength = '0000000000000000000000000000000000000000000000000000000000000002';
const encodedTokenValuesElements = '0000000000000000000000000000000000000000000000000000000000000001';
const assetDataWithBadTokenIds = `${assetDataWithNewTokenValuesOffset}${encodedTokenValuesLength}${encodedTokenValuesElements}`;
// execute transfer
await expectTransactionFailedAsync(
erc1155ProxyWrapper.transferFromAsync(
spender,
receiverContract,
erc1155Contract.address,
tokensToTransfer,
valuesToTransfer,
valueMultiplier,
receiverCallbackData,
authorized,
assetDataWithBadTokenIds,
),
RevertReason.InvalidValuesOffset,
);
});
it('should revert if token data resolves to outside the bounds of calldata', async () => {
// setup test parameters
const tokensToTransfer = fungibleTokens.slice(0, 1);
const valuesToTransfer = [fungibleValueToTransferLarge];
const valueMultiplier = valueMultiplierSmall;
const erc1155ContractAddress = erc1155Wrapper.getContract().address;
const assetData = assetDataUtils.encodeERC1155AssetData(
erc1155ContractAddress,
tokensToTransfer,
valuesToTransfer,
receiverCallbackData,
);
// The asset data we just generated will look like this:
// a7cb5fb7
// 0x 0000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e8082
// 0x20 0000000000000000000000000000000000000000000000000000000000000080
// 0x40 00000000000000000000000000000000000000000000000000000000000000c0
// 0x60 0000000000000000000000000000000000000000000000000000000000000100 // offset to token data
// 0x80 0000000000000000000000000000000000000000000000000000000000000001
// 0xA0 0000000000000000000000000000000100000000000000000000000000000000
// 0xC0 0000000000000000000000000000000000000000000000000000000000000001
// 0xE0 0000000000000000000000000000000000000000000000878678326eac900000
// 0x100 0000000000000000000000000000000000000000000000000000000000000004
// 0x120 0102030400000000000000000000000000000000000000000000000000000000
//
// We want to chan ge the offset to token data to point outside the calldata.
const encodedOffsetToTokenData = '0000000000000000000000000000000000000000000000000000000000000100';
const badEncodedOffsetToTokenData = '00000000000000000000000000000000000000000000000000000000000001c0';
const assetDataWithBadTokenDataOffset = assetData.replace(
encodedOffsetToTokenData,
badEncodedOffsetToTokenData,
);
// execute transfer
await expectTransactionFailedAsync(
erc1155ProxyWrapper.transferFromAsync(
spender,
receiverContract,
erc1155Contract.address,
tokensToTransfer,
valuesToTransfer,
valueMultiplier,
receiverCallbackData,
authorized,
assetDataWithBadTokenDataOffset,
),
RevertReason.InvalidDataOffset,
);
});
it('should revert if an element of token data lies to outside the bounds of calldata', async () => {
// setup test parameters
const tokensToTransfer = fungibleTokens.slice(0, 1);
const valuesToTransfer = [fungibleValueToTransferLarge];
const valueMultiplier = valueMultiplierSmall;
const erc1155ContractAddress = erc1155Wrapper.getContract().address;
const assetData = assetDataUtils.encodeERC1155AssetData(
erc1155ContractAddress,
tokensToTransfer,
valuesToTransfer,
receiverCallbackData,
);
// The asset data we just generated will look like this:
// a7cb5fb7
// 0x 0000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e8082
// 0x20 0000000000000000000000000000000000000000000000000000000000000080
// 0x40 00000000000000000000000000000000000000000000000000000000000000c0
// 0x60 0000000000000000000000000000000000000000000000000000000000000100 // offset to token data
// 0x80 0000000000000000000000000000000000000000000000000000000000000001
// 0xA0 0000000000000000000000000000000100000000000000000000000000000000
// 0xC0 0000000000000000000000000000000000000000000000000000000000000001
// 0xE0 0000000000000000000000000000000000000000000000878678326eac900000
// 0x100 0000000000000000000000000000000000000000000000000000000000000004
// 0x120 0102030400000000000000000000000000000000000000000000000000000000
//
// We want to chan ge the offset to token data to the end of calldata.
// Then we'll add an invalid length: we encode length of 33 but only add 32 elements.
const encodedOffsetToTokenData = '0000000000000000000000000000000000000000000000000000000000000100';
const newEcodedOffsetToTokenData = '0000000000000000000000000000000000000000000000000000000000000140';
const assetDataWithNewTokenDataOffset = assetData.replace(
encodedOffsetToTokenData,
newEcodedOffsetToTokenData,
);
const encodedTokenDataLength = '0000000000000000000000000000000000000000000000000000000000000021';
const encodedTokenDataElements = '0000000000000000000000000000000000000000000000000000000000000001';
const assetDataWithBadTokenData = `${assetDataWithNewTokenDataOffset}${encodedTokenDataLength}${encodedTokenDataElements}`;
// execute transfer
await expectTransactionFailedAsync(
erc1155ProxyWrapper.transferFromAsync(
spender,
receiverContract,
erc1155Contract.address,
tokensToTransfer,
valuesToTransfer,
valueMultiplier,
receiverCallbackData,
authorized,
assetDataWithBadTokenData,
),
RevertReason.InvalidDataOffset,
);
});
it('should transfer nothing if value is zero', async () => {
// setup test parameters
const tokenHolders = [spender, receiver];