-
Notifications
You must be signed in to change notification settings - Fork 1
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
The call to MsgValueSimulator with non zero msg.value will call to sender itself which will bypass the onlySelf check #153
Comments
The modifier |
miladpiri marked the issue as sponsor disputed |
@miladpiri knowing what we now know around #146, we know that if we perform a call with value, the The POC for the report seems to miss a key aspect around causing actual damage, however, wouldn't you agree that it did trigger a similar issue of being able to perform a User generated call with the |
I agree with you @GalloDaSballo So, IMHO, it is at most duplicate of #146 |
GalloDaSballo changed the severity to 2 (Med Risk) |
GalloDaSballo marked the issue as duplicate of #146 |
I think this is a case where I'd give the report 75% but because I don't have that option, am leaving as satisfactory, but awarding selected to the other one |
GalloDaSballo marked the issue as satisfactory |
Hi I think I should to further explain the exploit process of the issue. The point is not the value sent to MsgValueSimulator will be stuck in the contract. Instead, the value will be forward to another address. It's related to the call convention for the zksync's register. As I mentioned in the report:
Now I can only forward the value to the sender itself with calldata. It sounds ridiculous but can be very useful to compromise specific logics of Account Abstraction. Such as the The
The I also submitted a same issue as #146, which is #152 . I think the current issue causes deeper and more serious effects, and it has different core problem. So I submitted it as a separate high-risk issue. I don't think it's duplicate. Plus, if the judge ultimately decides it's duplicate, please do not modify the findingCount coefficient in the reward calculation. Because I also submitted #152 . Thanks 🤣. |
@5z1punch thank you for flagging, will check |
IMHO, it does not make sense. The report misses some important clarifications and thechnical details. |
Thanks for your prompt reply! I'm sorry I didn't explain it very clearly. Back to the MsgValueSimulator contract, the procedure for the first call is exactly what you said. But in the second call, because it is called from So in the second call, MsgValueSimulator ueses _getAbiParams function to get Thanks! |
Thanks for the clarification. |
GalloDaSballo marked the issue as not a duplicate |
@miladpiri @vladbochok were you able to validate the POC? What do you think? |
@GalloDaSballo @5z1punch you are not forgotten, I just need a bit more time to validate the issue. Will come back to you when checking it internally. Special thanks to the warden and judge for a very productive discussion. |
@5z1punch having a hard-time verifying your claims, could you point me to the logic in the registries that you believe is impacted? |
Hi, sorry for replying too late. The logic about the registries can't be found in the solidity/yul codes, I believe it's only operated and simulated by the era compiler https://github.com/matter-labs/era-compiler-solidity . As we know EVM is stack-based, the era compiler will translate the solidity/yul to the zksync's own bytecode for a register-based EVM. It uses some simulations for As for Let's first look at the normal MsgValueSimulator call flow, which is the first entry in the attack:
Because the called address is At the beginning of the
And the
It uses the And then, here's the last line of the fallback:
The main code of
The callAddr is This leads to the bypass case detailed in my report. But to be honest, I can only say that the registers will be overrided like that after the |
@5z1punch thank you for the extra info, I'm also looking at |
Hey @5z1punch @GalloDaSballo, I managed to reproduce the issue. @5z1punch is right, if Alice calls
Please note the pub struct FatPointer {
pub offset: u32,
pub memory_page: u32,
pub start: u32,
pub length: u32,
} And its raw representation: rawFatPointer = length || start || memory_page || offset Depending on the use case, a user could manipulate the So the length of the data should be 0, but manipulating other data is still possible. I see the impact of a non-unauthorized call to itself fallback function. It is indeed pretty bad, even though I don't know any smart contract that would suffer from this in practice. All in all, I confirm the issue and appreciate that deep research, thanks a lot @5z1punch! |
For a note here is the test that we add to our
|
Last but not least, even though the impact of the issue wasn't clear to us after triaging the report, the fix was done immediately after the end of the contest, before the launch. So this (and others) issues are not in production. https://explorer.zksync.io/address/0x0000000000000000000000000000000000008009#contract |
Thank you @vladbochok for the extra detail and am glad this was already addressed I do believe self-calling opens up to a category of exploits, especially for contracts that for example use try/catch or have "unusual" behaviour around transfers I believe we can agree that the finding is unique and at least of medium severity -> Incorrect behaviour, which can conditionally lose funds We must agree that the operation also can be viewed as account hijacking, in the sense that we can impersonate the receiving contract and then have it call itself These lead me to believe that a higher severity should be appropriate Have asked for advice to other judges with the goal of clarifying if there was sufficient information in the original submission, I believe the initial POC was valid but I want to get their perspective. Glad this was found and sorted |
GalloDaSballo marked the issue as selected for report |
While plenty of discussion has happened after Judging, the original finding has shown a valid POC that shows the following impact:
The discussion after that helped demonstrate the report's validity and the Sponsor has already mitigated the potential risk. The ability for a specific contract to call self can be met with some skepticism in terms of its impact, however, I believe that in different scenarios, the severity would easily be raised to High. For example:
If those contracts were in-scope and the setup demonstrated in the finding was not patched, the finding would have easily been rated as High Severity. In this case, those contracts are not in-scope, so I would maintain a Med Severity, because that's reliant on the specific integrators using that pattern. In contrast to
Given the following considerations, I have asked myself whether this is a type of risk that would in any way be imputable to the integrator as a quirk, and at this time I cannot justify that For the logic above, because the finding has shown a way to break a very strong expectation that a contract cannot call itself unless programmed for it, considering this as The Sponsor has already mitigated the finding at the time of writing |
GalloDaSballo changed the severity to 3 (High Risk) |
Lines of code
https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/MsgValueSimulator.sol#L22-L63
Vulnerability details
Impact
First, I need to clarify, there may be more serious ways to exploit this issue. Due to the lack of time and documents, I cannot complete further exploit. The current exploit has only achieved the impact in the title. I will expand the possibility of further exploit in the poc chapter.
The call to MsgValueSimulator with non zero msg.value will call to sender itself with the msg.data. It means that if you can make a contract or a custom account call to specified address with non zero msg.value (that's very common in withdrawal functions and smart contract wallets), you can make the contract/account call itself. And if you can also control the calldata, you can make the contract/account call its functions by itself.
It will bypass some security check with the msg.sender, or break the accounting logic of some contracts which use the msg.sender as account name.
For example the
onlySelf
modifier in the ContractDepolyer contract:Proof of Concept
The
MsgValueSimulator
use themimicCall
to forward the original call.And if the
to
address is theMsgValueSimulator
address, it will go back to theMsgValueSimulator.fallback
function again.The fallback function will Extract the
value
to send, isSystemCall flag and theto
address from the extraAbi params(r3,r4,r5) in the_getAbiParams
function. But it's different from the first call to theMsgValueSimulator
. The account usesEfficientCall.rawCall
function to call theMsgValueSimulator.fallback
in the first call. For example, code inDefaultAccount._execute
:The rawCall will simulate
system_call_byref
opcode to call theMsgValueSimulator
. And thesystem_call_byref
will write the r3-r5 registers which are read as the above extraAbi params.But the second call is sent by
EfficientCall.mimicCall
, as the return value explained in the document https://github.com/code-423n4/2023-03-zksync/blob/main/docs/VM-specific_v1.3.0_opcodes_simulation.pdf,mimicCall
will mess up the registers and will use r1-r4 for standard ABI convention and r5 for the extra who_to_mimic arg. So extraAbi params(r3-r5) read by_getAbiParams
will be messy data. It can lead to very serious consequences, because the r3 will be used as the msg.value, and the r4 will be used as theto
address in the finalmimicCall
. It means that the contract will send a different(greater) value to a different address, which is unexpected in the original call.I really don't know how to write a complex test to verify register changes in the era-compiler-tester. So to find out how to control the registers, I use the repo https://github.com/matter-labs/zksync-era and replace the etc/system-contracts/ codes with the lastest version in the contest, and write a integration test.
The L2EthToken Transfer event logs:
There are two l2 eth token transaction in addition to gas processing. And the value sent to the
MsgValueSimulator
will stuck in the contract forever.I found that the r4(to) is always msg.sender, the r5(mask) is always 0x1, and if the length of the calldata is 0, the r3(value) will be
0x2129c0000000a00000000
, and if the length > 0, r3(value) will be0x215800000000a00000000 + calldata.length << 96
. So in this case, the balance of the sender should be at least 0x2129c0000000a00000000 wei to finish the whole transaction whitout reverting.I did not find any document about the
standard ABI convention
mentioned in the VM-specific_v1.3.0_opcodes_simulation.pdf and the r5 is also not really the extra who_to_mimic arg. I didn't make a more serious exploit due to lack of time. I'd like more documentation about register call conventions to verify the possibility of manipulating registers.Tools Used
Manual review
Recommended Mitigation Steps
Check the
to
address in theMsgValueSimulator
contract. Theto
address must not be theMsgValueSimulator
address.The text was updated successfully, but these errors were encountered: