-
Notifications
You must be signed in to change notification settings - Fork 926
/
FallbackManager.sol
82 lines (74 loc) · 3.96 KB
/
FallbackManager.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
import "../common/SelfAuthorized.sol";
/**
* @title Fallback Manager - A contract managing fallback calls made to this contract
* @author Richard Meissner - @rmeissner
*/
abstract contract FallbackManager is SelfAuthorized {
event ChangedFallbackHandler(address indexed handler);
// keccak256("fallback_manager.handler.address")
bytes32 internal constant FALLBACK_HANDLER_STORAGE_SLOT = 0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5;
/**
* @notice Internal function to set the fallback handler.
* @param handler contract to handle fallback calls.
*/
function internalSetFallbackHandler(address handler) internal {
/*
If a fallback handler is set to self, then the following attack vector is opened:
Imagine we have a function like this:
function withdraw() internal authorized {
withdrawalAddress.call.value(address(this).balance)("");
}
If the fallback method is triggered, the fallback handler appends the msg.sender address to the calldata and calls the fallback handler.
A potential attacker could call a Safe with the 3 bytes signature of a withdraw function. Since 3 bytes do not create a valid signature,
the call would end in a fallback handler. Since it appends the msg.sender address to the calldata, the attacker could craft an address
where the first 3 bytes of the previous calldata + the first byte of the address make up a valid function signature. The subsequent call would result in unsanctioned access to Safe's internal protected methods.
For some reason, solidity matches the first 4 bytes of the calldata to a function signature, regardless if more data follow these 4 bytes.
*/
require(handler != address(this), "GS400");
bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT;
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(slot, handler)
}
}
/**
* @notice Set Fallback Handler to `handler` for the Safe.
* @dev Only fallback calls without value and with data will be forwarded.
* This can only be done via a Safe transaction.
* Cannot be set to the Safe itself.
* @param handler contract to handle fallback calls.
*/
function setFallbackHandler(address handler) public authorized {
internalSetFallbackHandler(handler);
emit ChangedFallbackHandler(handler);
}
// @notice Forwards all calls to the fallback handler if set. Returns 0 if no handler is set.
// @dev Appends the non-padded caller address to the calldata to be optionally used in the handler
// The handler can make us of `HandlerContext.sol` to extract the address.
// This is done because in the next call frame the `msg.sender` will be FallbackManager's address
// and having the original caller address may enable additional verification scenarios.
// solhint-disable-next-line payable-fallback,no-complex-fallback
fallback() external {
bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT;
// solhint-disable-next-line no-inline-assembly
assembly {
let handler := sload(slot)
if iszero(handler) {
return(0, 0)
}
calldatacopy(0, 0, calldatasize())
// The msg.sender address is shifted to the left by 12 bytes to remove the padding
// Then the address without padding is stored right after the calldata
mstore(calldatasize(), shl(96, caller()))
// Add 20 bytes for the address appended add the end
let success := call(gas(), handler, 0, 0, add(calldatasize(), 20), 0, 0)
returndatacopy(0, 0, returndatasize())
if iszero(success) {
revert(0, returndatasize())
}
return(0, returndatasize())
}
}
}