Skip to content

Commit 87d5f3e

Browse files
committed
test: implement a test for the critical exploit
2 parents 6634b0a + 05ffca4 commit 87d5f3e

File tree

2 files changed

+182
-1
lines changed

2 files changed

+182
-1
lines changed
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.13;
3+
4+
import "forge-std/Test.sol";
5+
import { IncentivizedMockEscrow } from "../../../src/apps/mock/IncentivizedMockEscrow.sol";
6+
import { IIncentivizedMessageEscrow } from "../../../src/interfaces/IIncentivizedMessageEscrow.sol";
7+
import { TestCommon } from "../../TestCommon.t.sol";
8+
import { ICrossChainReceiver } from "../../../src/interfaces/ICrossChainReceiver.sol";
9+
10+
11+
contract TargetExploit is ICrossChainReceiver {
12+
13+
event ACK(bytes);
14+
15+
IIncentivizedMessageEscrow immutable MESSAGE_ESCROW;
16+
17+
constructor(IIncentivizedMessageEscrow messageEscrow_) {
18+
MESSAGE_ESCROW = messageEscrow_;
19+
}
20+
21+
function submitMessage(
22+
bytes32 destinationIdentifier,
23+
bytes calldata destinationAddress,
24+
bytes calldata message,
25+
IIncentivizedMessageEscrow.IncentiveDescription calldata incentive,
26+
uint64 deadline
27+
) external payable returns(uint256 gasRefund, bytes32 messageIdentifier) {
28+
(gasRefund, messageIdentifier) = MESSAGE_ESCROW.submitMessage{value: msg.value}(
29+
destinationIdentifier,
30+
destinationAddress,
31+
message,
32+
incentive,
33+
deadline
34+
);
35+
}
36+
37+
function receiveAck(bytes32 /* destinationIdentifier */, bytes32 /* messageIdentifier */, bytes calldata acknowledgement) external {
38+
emit ACK(acknowledgement);
39+
}
40+
41+
function receiveMessage(bytes32 /* sourceIdentifierbytes */, bytes32 /* messageIdentifier */, bytes calldata /* fromApplication */, bytes calldata /* message */) pure external returns(bytes memory acknowledgement) {
42+
return hex"";
43+
}
44+
}
45+
46+
contract DestinationHelper is ICrossChainReceiver {
47+
IIncentivizedMessageEscrow immutable MESSAGE_ESCROW;
48+
49+
constructor(IIncentivizedMessageEscrow messageEscrow_) {
50+
MESSAGE_ESCROW = messageEscrow_;
51+
}
52+
53+
function submitMessage(
54+
bytes32 destinationIdentifier,
55+
bytes calldata destinationAddress,
56+
bytes calldata message,
57+
IIncentivizedMessageEscrow.IncentiveDescription calldata incentive,
58+
uint64 deadline
59+
) external payable returns(uint256 gasRefund, bytes32 messageIdentifier) {
60+
(gasRefund, messageIdentifier) = MESSAGE_ESCROW.submitMessage{value: msg.value}(
61+
destinationIdentifier,
62+
destinationAddress,
63+
message,
64+
incentive,
65+
deadline
66+
);
67+
}
68+
69+
function receiveAck(bytes32 destinationIdentifier, bytes32 messageIdentifier, bytes calldata acknowledgement) pure external {}
70+
71+
function receiveMessage(bytes32 /* sourceIdentifierbytes */, bytes32 /* messageIdentifier */, bytes calldata /* fromApplication */, bytes calldata message) pure external returns(bytes memory acknowledgement) {
72+
return message;
73+
}
74+
75+
}
76+
77+
contract ReemitAckAnywhereExploitTest is TestCommon {
78+
79+
TargetExploit targetToExploit;
80+
81+
DestinationHelper destinationHelper;
82+
83+
function setUp() override public {
84+
super.setUp();
85+
86+
targetToExploit = new TargetExploit(escrow);
87+
destinationHelper = new DestinationHelper(escrow);
88+
89+
// We need to set escrow as an approved sender and destination on targetToExplot
90+
// We need to set escrow as an approved caller on destinationHelper.
91+
vm.prank(address(targetToExploit));
92+
escrow.setRemoteImplementation(_DESTINATION_IDENTIFIER, abi.encode(address(escrow)));
93+
94+
vm.prank(address(destinationHelper));
95+
escrow.setRemoteImplementation(_DESTINATION_IDENTIFIER, abi.encode(address(this)));
96+
}
97+
98+
function test_exploit_target_contract() public {
99+
// This explot is about using the fact that anyone can call a destination escrow.
100+
101+
// We will start by sending a message from TargetExplot:
102+
103+
// vm.recordLogs();
104+
(, bytes32 messageIdentifier) = targetToExploit.submitMessage{value: _getTotalIncentive(_INCENTIVE)}(
105+
_DESTINATION_IDENTIFIER,
106+
abi.encodePacked(
107+
bytes1(0x14),
108+
bytes32(0),
109+
abi.encode(address(targetToExploit))
110+
),
111+
hex"04e110",
112+
_INCENTIVE,
113+
0
114+
);
115+
116+
// Now we need to construct a message to attack with.
117+
118+
bytes memory explotMessage = abi.encodePacked(
119+
bytes1(0),
120+
bytes32(messageIdentifier), // Remember, we can set anything.
121+
abi.encodePacked(
122+
uint8(20),
123+
bytes32(0),
124+
abi.encode(address(targetToExploit))
125+
), // We need to set this to the contract we want to exploit.
126+
abi.encodePacked(
127+
uint8(20),
128+
bytes32(0),
129+
abi.encode(address(destinationHelper))
130+
), // Our application so we know it goes through
131+
bytes8(0), // deadline, don't care about
132+
bytes6(uint48(600000)), // max gas, sets set high.
133+
abi.encodePacked(
134+
hex"deaddeaddeaddead"
135+
)
136+
);
137+
138+
bytes memory nonLegitMessage = abi.encodePacked(
139+
bytes32(uint256(uint160(address(this)))),
140+
_DESTINATION_IDENTIFIER,
141+
_DESTINATION_IDENTIFIER,
142+
explotMessage
143+
);
144+
145+
(uint8 v, bytes32 r, bytes32 s) = signMessageForMock(nonLegitMessage);
146+
bytes memory nonLegitMessageContext = abi.encode(v, r, s);
147+
148+
// lets execute the non-legit message
149+
150+
vm.recordLogs();
151+
escrow.processPacket(nonLegitMessageContext, nonLegitMessage, bytes32(abi.encode(address(0))));
152+
Vm.Log[] memory entries = vm.getRecordedLogs();
153+
154+
(, , bytes memory ackMessage) = abi.decode(entries[1].data, (bytes32, bytes, bytes));
155+
156+
bytes memory preparedExploitMessage = this.memorySlice(ackMessage, 64);
157+
158+
vm.recordLogs();
159+
160+
vm.expectRevert(abi.encodeWithSignature("CannotRetryWrongMessage(bytes32,bytes32)", bytes32(0), keccak256(preparedExploitMessage)));
161+
escrow.reemitAckMessage(
162+
_DESTINATION_IDENTIFIER,
163+
abi.encode(address(escrow)),
164+
preparedExploitMessage
165+
);
166+
167+
// The message fails above so we can't continue. But if we could, this is what we would do.
168+
169+
// (, , bytes memory reAckMessage) = abi.decode(entries[1].data, (bytes32, bytes, bytes));
170+
171+
// bytes memory reAckMessageWithSender = abi.encodePacked(
172+
// bytes32(uint256(uint160(address(escrow)))),
173+
// reAckMessage
174+
// );
175+
176+
// (v, r, s) = signMessageForMock(reAckMessageWithSender);
177+
// bytes memory reAckMessageContext = abi.encode(v, r, s);
178+
179+
// escrow.processPacket(reAckMessageContext, reAckMessageWithSender, bytes32(abi.encode(address(0))));
180+
}
181+
}

test/IncentivizedMessageEscrow/ReemitAckMessage.t.sol renamed to test/IncentivizedMessageEscrow/reemitAck/ReemitAckMessage.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
pragma solidity ^0.8.13;
33

44
import "forge-std/Test.sol";
5-
import { TestCommon } from "../TestCommon.t.sol";
5+
import { TestCommon } from "../../TestCommon.t.sol";
66

77
contract ReemitAckMessageTest is TestCommon {
88
event Message(

0 commit comments

Comments
 (0)