-
Notifications
You must be signed in to change notification settings - Fork 71
test: Intent's static + dynamic offsets' field corruptions #312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
ccd1631 to
f3d07dd
Compare
|
@legion2002 @howydev plz take a look and lemme know of improvements |
|
@smitrajput Thanks we'll look into it soon, these tests will be very helpful! |
|
Looking forward to the review @legion2002! |
test/Account.t.sol
Outdated
| bool success; | ||
| bytes memory returnData; | ||
|
|
||
| console.log("Test 1: Main Intent struct offset corruption"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't use console.logs in our tests, could you leave these as comments instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also do this for the rest of the console logs, please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
test/Account.t.sol
Outdated
| // executionData offset (bytes 64-95 relative to start, or 32-63 in Intent struct) | ||
| mstore(add(intentPtr, 32), 0x10000000000000001) // 2^64 + 1 | ||
| } | ||
| // assertEq(oc.execute(maliciousCalldata), bytes4(keccak256("VerifiedCallError()"))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove this
I assume this doesn't pass because it reverts with a generic EVM error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test/Account.t.sol
Outdated
| assembly { | ||
| let dataPtr := add(maliciousCalldata, 0x20) // Skip bytes length prefix | ||
| // CORRUPT MAIN OFFSET (Bytes 0-31) - Points to Intent struct start | ||
| mstore(dataPtr, 0x10000000000000000) // 2^64 (strictly greater than 2^64-1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if works
If dataPtr is an encoded intent, wouldn't add(maliciousCalldata, 0x20) point to intent.eoa?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, as mentioned in that comment, add(maliciousCalldata, 0x20) points to the main offset of the Intent struct, as the first 32 bytes of maliciousCalldata store the length of the encoded Intent struct. Plz check out the calldata layout here, to visualise it. I've also simplified the yul there, for easier understanding.
test/Account.t.sol
Outdated
| // assertEq(oc.execute(maliciousCalldata), bytes4(keccak256("VerifiedCallError()"))); | ||
| (success, returnData) = | ||
| address(oc).call(abi.encodeWithSignature("execute(bytes)", maliciousCalldata)); | ||
| assertEq(success, false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you think of a better check here instead of a generic execution failure? im a little worried that some of these tests are failing not because of corrupted calldata, so the test is inaccurate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
managed to add better checks for 2 (Test 2 and Test 6) of the 8 dynamic offset corruptions, but couldn't find anything significant for the remaining 6 as they either
- fail and return the generic
EvmError: Reverton corrupting with out-of-bound values like 2^64, with a return value0x, so further try/catching or error logging doesn't help - or pass and return
0x00000000with within bounds values like0x300,0a20
Also note that these tests are indeed failing because of offset corruption, as I corrupted all the 13 other static fields and we're seeing defined errors being thrown and caught for all of them.
howydev
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hi there, thanks for making this submission!
i'm slightly worried that some of these tests are not checking what they're supposed to be checking. would love if there's a way to do a check other than a generic execution failure, since there are a lot of reasons that error would happen
(for instance, i suspect that for one of the tests we're switching intent.eoa to a different eoa. in this case the execution call would fail in a similar way)
99f9d06 to
3a4c292
Compare
|
thanks a lot for the review @howydev I double checked to see if the offset corruption is correct, and we are indeed corrupting the exact offsets. To help with the calldata corruption visualisation, plz check out this calldata layout in the tests, so we're sure we're corrupting the correct offsets. Also added better checks for 2 (Test 2 and Test 6) of the 8 dynamic offset corruptions, but couldn't find anything significant for the remaining 6 as they either
To further prove that we're not getting those generic reverts bcoz of any adjacent fields' corruption (or any other random reason), I corrupted all the remaining 13 static fields and we're throwing and catching well-defined errors for all of them. Plz find those tests here. This proves that the execution call fails differently for dynamic offsets corruption and static fields corruption. You make a valid point that the generic |
3a4c292 to
faed602
Compare
|
hey, apologies for the delay, didnt see that you replied will allocate some time later this week for a review |
|
hey @smitrajput - really appreciate the time taken for this review, but we're likely going to switch to formats that remove all calldata offsets, using packed mode encoding instead of encoding a struct By removing offsets, we kill 2 birds with a single stone - reduce the cost of operations, and no more security issues from custom abi encodings |
|
that's a cool idea @howydev, curious to have a look at the updated implementation and see if I can reuse these tests (at least the static fields corruption part and add the missing dynamic data corruption part too), to check the new and different security implications of the packed encoding |
|
changes are ready: #365 instead of having a struct that comes with offsets, we have a custom encoding for our would love your eyes on this if you have spare time, and if you think of any tests we should add feel free to propose! |
|
sound! taking a look asap |



Test Description
This is a good first step for issues 201 and 212, testing potentially unsafe decoding by corrupting the 13 static fields and offsets of all 7 dynamic fields of
Intentstruct, along with Intent's main offset, during calls toOrchestrator.execute(), targeting naked decoding at_extractIntent(),selfCallPayVerifyCall537021665(),_extractPreCall()andIthacaAccount.pay().Tests are split into 21 functions: 13 for corrupting static fields, 7 for dynamic field offsets and 1 corrupting the main intent offset.
Result
All tests pass clean without the corruption (base case in existing tests), and throw
EvmError: Revertafter corruption for 6 dynamic field offset corruptions as expected, caught asfalsevalue returned on doing a.call() to Orchestrator.execute(). The remaining 15 return well-defined errors in the codebase which are caught neatly. Implying corrupted offsets are unable to induce unintended behaviours and the decoding throughout the call fromOrchestrator.execute()toIthacaAccount.pay()is safe from this attack.Potential Improvement
Would have preferred a simpler multichain intent setup, to test the corrupted
settlerContextfield than the current one, which reuses most ofOrchestrator.t.sol's testMultichainIntent() setup. Improvement suggestions welcomed.Future Work
Note that tests for corrupting the data of dynamic fields still remain. Worth adding in a future PR.