Skip to content

Commit

Permalink
Fix issues caused by abi.decode reverting (#3552)
Browse files Browse the repository at this point in the history
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
  • Loading branch information
Amxx and frangio committed Jul 18, 2022
1 parent d50e608 commit 628a6e2
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 2 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -15,6 +15,11 @@

ERC-721 integrators that interpret contract state from events should make sure that they implement the clearing of approval that is implicit in every transfer according to the EIP. Previous versions of OpenZeppellin Contracts emitted an explicit `Approval` event even though it was not required by the specification, and this is no longer the case.

## 4.7.1

* `SignatureChecker`: Fix an issue that causes `isValidSignatureNow` to revert when the target contract returns ill-encoded data. ([#3552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3552))
* `ERC165Checker`: Fix an issue that causes `supportsInterface` to revert when the target contract returns ill-encoded data. ([#3552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3552))

## 4.7.0 (2022-06-29)

* `TimelockController`: Migrate `_call` to `_execute` and allow inheritance and overriding similar to `Governor`. ([#3317](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3317))
Expand Down
9 changes: 9 additions & 0 deletions contracts/mocks/ERC1271WalletMock.sol
Expand Up @@ -15,3 +15,12 @@ contract ERC1271WalletMock is Ownable, IERC1271 {
return ECDSA.recover(hash, signature) == owner() ? this.isValidSignature.selector : bytes4(0);
}
}

contract ERC1271MaliciousMock is IERC1271 {
function isValidSignature(bytes32, bytes memory) public pure override returns (bytes4) {
assembly {
mstore(0, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
return(0, 32)
}
}
}
12 changes: 12 additions & 0 deletions contracts/mocks/ERC165/ERC165MaliciousData.sol
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract ERC165MaliciousData {
function supportsInterface(bytes4) public view returns (bool) {
assembly {
mstore(0, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
return(0, 32)
}
}
}
4 changes: 3 additions & 1 deletion contracts/utils/cryptography/SignatureChecker.sol
Expand Up @@ -35,6 +35,8 @@ library SignatureChecker {
(bool success, bytes memory result) = signer.staticcall(
abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature)
);
return (success && result.length == 32 && abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector);
return (success &&
result.length == 32 &&
abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector));
}
}
2 changes: 1 addition & 1 deletion contracts/utils/introspection/ERC165Checker.sol
Expand Up @@ -108,6 +108,6 @@ library ERC165Checker {
bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId);
(bool success, bytes memory result) = account.staticcall{gas: 30000}(encodedParams);
if (result.length < 32) return false;
return success && abi.decode(result, (bool));
return success && abi.decode(result, (uint256)) > 0;
}
}
10 changes: 10 additions & 0 deletions test/utils/cryptography/SignatureChecker.test.js
Expand Up @@ -4,6 +4,7 @@ const { expect } = require('chai');

const SignatureCheckerMock = artifacts.require('SignatureCheckerMock');
const ERC1271WalletMock = artifacts.require('ERC1271WalletMock');
const ERC1271MaliciousMock = artifacts.require('ERC1271MaliciousMock');

const TEST_MESSAGE = web3.utils.sha3('OpenZeppelin');
const WRONG_MESSAGE = web3.utils.sha3('Nope');
Expand All @@ -14,6 +15,7 @@ contract('SignatureChecker (ERC1271)', function (accounts) {
before('deploying', async function () {
this.signaturechecker = await SignatureCheckerMock.new();
this.wallet = await ERC1271WalletMock.new(signer);
this.malicious = await ERC1271MaliciousMock.new();
this.signature = await web3.eth.sign(TEST_MESSAGE, signer);
});

Expand Down Expand Up @@ -67,5 +69,13 @@ contract('SignatureChecker (ERC1271)', function (accounts) {
this.signature,
)).to.equal(false);
});

it('with malicious wallet', async function () {
expect(await this.signaturechecker.isValidSignatureNow(
this.malicious.address,
toEthSignedMessageHash(TEST_MESSAGE),
this.signature,
)).to.equal(false);
});
});
});
33 changes: 33 additions & 0 deletions test/utils/introspection/ERC165Checker.test.js
Expand Up @@ -4,6 +4,7 @@ const { expect } = require('chai');

const ERC165CheckerMock = artifacts.require('ERC165CheckerMock');
const ERC165MissingData = artifacts.require('ERC165MissingData');
const ERC165MaliciousData = artifacts.require('ERC165MaliciousData');
const ERC165NotSupported = artifacts.require('ERC165NotSupported');
const ERC165InterfacesSupported = artifacts.require('ERC165InterfacesSupported');

Expand Down Expand Up @@ -51,6 +52,38 @@ contract('ERC165Checker', function (accounts) {
});
});

context('ERC165 malicious return data', function () {
beforeEach(async function () {
this.target = await ERC165MaliciousData.new();
});

it('does not support ERC165', async function () {
const supported = await this.mock.supportsERC165(this.target.address);
expect(supported).to.equal(false);
});

it('does not support mock interface via supportsInterface', async function () {
const supported = await this.mock.supportsInterface(this.target.address, DUMMY_ID);
expect(supported).to.equal(false);
});

it('does not support mock interface via supportsAllInterfaces', async function () {
const supported = await this.mock.supportsAllInterfaces(this.target.address, [DUMMY_ID]);
expect(supported).to.equal(false);
});

it('does not support mock interface via getSupportedInterfaces', async function () {
const supported = await this.mock.getSupportedInterfaces(this.target.address, [DUMMY_ID]);
expect(supported.length).to.equal(1);
expect(supported[0]).to.equal(false);
});

it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () {
const supported = await this.mock.supportsERC165InterfaceUnchecked(this.target.address, DUMMY_ID);
expect(supported).to.equal(true);
});
});

context('ERC165 not supported', function () {
beforeEach(async function () {
this.target = await ERC165NotSupported.new();
Expand Down

0 comments on commit 628a6e2

Please sign in to comment.