Skip to content

Commit 8fb5f57

Browse files
Amxxfrangio
andcommitted
Avoid returnbomb in ERC165Checker (#3587)
Co-authored-by: Francisco Giordano <[email protected]> (cherry picked from commit dc4869e)
1 parent 67b2572 commit 8fb5f57

File tree

3 files changed

+51
-3
lines changed

3 files changed

+51
-3
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.0;
4+
5+
import "../../utils/introspection/IERC165.sol";
6+
7+
contract ERC165ReturnBombMock is IERC165 {
8+
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
9+
if (interfaceId == type(IERC165).interfaceId) {
10+
assembly {
11+
mstore(0, 1)
12+
}
13+
}
14+
assembly {
15+
return(0, 101500)
16+
}
17+
}
18+
}

contracts/utils/introspection/ERC165Checker.sol

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,19 @@ library ERC165Checker {
105105
* Interface identification is specified in ERC-165.
106106
*/
107107
function _supportsERC165Interface(address account, bytes4 interfaceId) private view returns (bool) {
108+
// prepare call
108109
bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId);
109-
(bool success, bytes memory result) = account.staticcall{gas: 30000}(encodedParams);
110-
if (result.length < 32) return false;
111-
return success && abi.decode(result, (uint256)) > 0;
110+
111+
// perform static call
112+
bool success;
113+
uint256 returnSize;
114+
uint256 returnValue;
115+
assembly {
116+
success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
117+
returnSize := returndatasize()
118+
returnValue := mload(0x00)
119+
}
120+
121+
return success && returnSize >= 0x20 && returnValue > 0;
112122
}
113123
}

test/utils/introspection/ERC165Checker.test.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const ERC165MissingData = artifacts.require('ERC165MissingData');
77
const ERC165MaliciousData = artifacts.require('ERC165MaliciousData');
88
const ERC165NotSupported = artifacts.require('ERC165NotSupported');
99
const ERC165InterfacesSupported = artifacts.require('ERC165InterfacesSupported');
10+
const ERC165ReturnBombMock = artifacts.require('ERC165ReturnBombMock');
1011

1112
const DUMMY_ID = '0xdeadbeef';
1213
const DUMMY_ID_2 = '0xcafebabe';
@@ -243,4 +244,23 @@ contract('ERC165Checker', function (accounts) {
243244
expect(supported[0]).to.equal(false);
244245
});
245246
});
247+
248+
it('Return bomb resistance', async function () {
249+
this.target = await ERC165ReturnBombMock.new();
250+
251+
const tx1 = await this.mock.supportsInterface.sendTransaction(this.target.address, DUMMY_ID);
252+
expect(tx1.receipt.gasUsed).to.be.lessThan(120000); // 3*30k + 21k + some margin
253+
254+
const tx2 = await this.mock.getSupportedInterfaces.sendTransaction(
255+
this.target.address,
256+
[
257+
DUMMY_ID,
258+
DUMMY_ID_2,
259+
DUMMY_ID_3,
260+
DUMMY_UNSUPPORTED_ID,
261+
DUMMY_UNSUPPORTED_ID_2,
262+
],
263+
);
264+
expect(tx2.receipt.gasUsed).to.be.lessThan(250000); // (2+5)*30k + 21k + some margin
265+
});
246266
});

0 commit comments

Comments
 (0)