-
Notifications
You must be signed in to change notification settings - Fork 963
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
Enabling yul optimizer and viaIR causes stack too deep error using Solidity 0.8.19 #544
Comments
Just to clarify what I originally said in https://github.com/ethereum/solidity/pull/14086/files#discussion_r1157464962:
Your code worked on earlier 0.8.x releases without marking assembly blocks as memory safe because it was just slightly below the complexity that triggers "Stack too deep" here. Later changes in the compiler seem to have pushed it over the edge. The compiler has a mechanism to keep it compiling (i.e. the memory mover) but that mechanism won't work in presence of memory-unsafe assembly. Your blocks may very well be memory-safe but the compiler only assumes that when they a block does not touch memory. It it does, you have to verify yourself if it's really memory safe and mark it as such manually. The memory-safe annotation was not available in 0.7.x but there's an alternative mechanism via a magic |
@cameel, to clarify if I'm getting it right: all assembly blocks must be memory safe for the compiler to use that mechanism? |
Yes. All blocks that are included in the code a contract is built from. So e.g. if you have two different contracts in a file and they don't inherit from each other, blocks in one do not influence the other. But if both use a library function imported from a different file and that function has an unsafe block, both are affected. Also, like I said, some blocks are implicitly safe and do not need to be explicitly annotated:
but an explicit annotation won't hurt either. One way to check if all blocks are memory safe and there's nothing preventing the memory mover from kicking in is to check the IR output ( |
Hey @cameel I'm looking into the issue now, and I found an assembly block that's not memory safe: Here we write data to the memory that may go out of the scratch space bounds, but in our case, it is OK since we never access the memory outside the assembly block. It does compile when marked memory safe. Is there a potential drawback from a security standpoint in doing so? The solidity docs about memory safety tell that memory shall be allocated via the free memory pointer in all cases. I would like to confirm if that's the only option |
Yes, this is a security issue. It's not overly likely to become an actual issue, but by telling the compiler that this snippet is memory safe, you allow it to move variables like The issue is really on our side, since we should do a better job of localizing stack issues - it's unlikely that there is actually a stack issue in this assembly snippet, but the compiler will currently refuse to move variables to memory anywhere if it sees any memory-unsafe assembly, rather than merely refusing to move variables that can possibly overlap with such memory-unsafe assembly. But short of that being fixed on our end, I'd recommend not to declare this assembly block as memory-safe as is without changing the way it uses memory (by actually reading the value of the free memory pointer and only using memory beyond that offset, rather than overwriting arbitrarily large memory starting at offset zero). |
For context, we are re-opening this issue as there are still some non-
There may be other instances as well. |
I'm reopening this because the error is back again. After a quick investigation, there seem to be two problematic spots:
|
Race condition! |
Hey @ekpyron and @cameel - sorry to necro this thread a bit, but we are changing all assembly to be memory safe right now, and wanted to double check something with you that isn’t clear to us. In particular the Solidity docs state that the following memory use is safe:
One thing I wanted to confirm from @ekpyron’s comment regarding:
Does the compiler guarantee this to happen before the assembly block? Otherwise, couldn’t you potentially overwrite data written to after the free memory pointer if the allocation happens within the assembly block? I.e. imagine an assembly block that does the following:
Now to clarify, is the memory location of Edit: I did some investigation, and it looks like the compiler is emitting Sample code that causes the compiler to move variables to memory// SPDX-License-Identifier: 0BSD
pragma solidity >=0.7 <0.9;
contract A {
fallback() external {
assembly ("memory-safe") {
let t := mload(0x40)
calldatacopy(t, 0x00, 0x20)
let x00 := sload(0x00)
let x01 := add(x00, sload(0x01))
let x02 := add(x01, add(x00, sload(0x02)))
let x03 := add(x02, add(x01, add(x00, sload(0x03))))
let x04 := add(x03, add(x02, add(x01, add(x00, sload(0x04)))))
let x05 := add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x05))))))
let x06 := add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x06)))))))
let x07 := add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x07))))))))
let x08 := add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x08)))))))))
let x09 := add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x09))))))))))
let x0a := add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x0a)))))))))))
let x0b := add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x0b))))))))))))
let x0c := add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x0c)))))))))))))
let x0d := add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x0d))))))))))))))
let x0e := add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x0e)))))))))))))))
let x0f := add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x0f))))))))))))))))
let x10 := add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x10)))))))))))))))))
let x11 := add(x10, add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x11))))))))))))))))))
let x12 := add(x11, add(x10, add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x12)))))))))))))))))))
let x13 := add(x12, add(x11, add(x10, add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x13))))))))))))))))))))
let x14 := add(x13, add(x12, add(x11, add(x10, add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x14)))))))))))))))))))))
let x15 := add(x14, add(x13, add(x12, add(x11, add(x10, add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x15))))))))))))))))))))))
let x16 := add(x15, add(x14, add(x13, add(x12, add(x11, add(x10, add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x16)))))))))))))))))))))))
let x17 := add(x16, add(x15, add(x14, add(x13, add(x12, add(x11, add(x10, add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x17))))))))))))))))))))))))
let x18 := add(x17, add(x16, add(x15, add(x14, add(x13, add(x12, add(x11, add(x10, add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x18)))))))))))))))))))))))))
let x19 := add(x18, add(x17, add(x16, add(x15, add(x14, add(x13, add(x12, add(x11, add(x10, add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x19))))))))))))))))))))))))))
let x1a := add(x19, add(x18, add(x17, add(x16, add(x15, add(x14, add(x13, add(x12, add(x11, add(x10, add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x1a)))))))))))))))))))))))))))
let x1b := add(x1a, add(x19, add(x18, add(x17, add(x16, add(x15, add(x14, add(x13, add(x12, add(x11, add(x10, add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x1b))))))))))))))))))))))))))))
let x1c := add(x1b, add(x1a, add(x19, add(x18, add(x17, add(x16, add(x15, add(x14, add(x13, add(x12, add(x11, add(x10, add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x1c)))))))))))))))))))))))))))))
let x1d := add(x1c, add(x1b, add(x1a, add(x19, add(x18, add(x17, add(x16, add(x15, add(x14, add(x13, add(x12, add(x11, add(x10, add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x1d))))))))))))))))))))))))))))))
let x1e := add(x1d, add(x1c, add(x1b, add(x1a, add(x19, add(x18, add(x17, add(x16, add(x15, add(x14, add(x13, add(x12, add(x11, add(x10, add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x1e)))))))))))))))))))))))))))))))
let x1f := add(x1e, add(x1d, add(x1c, add(x1b, add(x1a, add(x19, add(x18, add(x17, add(x16, add(x15, add(x14, add(x13, add(x12, add(x11, add(x10, add(x0f, add(x0e, add(x0d, add(x0c, add(x0b, add(x0a, add(x09, add(x08, add(x07, add(x06, add(x05, add(x04, add(x03, add(x02, add(x01, add(x00, sload(0x1f))))))))))))))))))))))))))))))))
mstore(add(t, 0x20), x1f)
return(t, 0x40)
}
}
} |
This PR modifies the account fallback code to no longer use allocations, as they are not required for memory-safety. [From the docs](https://docs.soliditylang.org/en/v0.8.22/assembly.html#memory-safety): > In particular, a memory-safe assembly block may only access the following memory ranges: > > * [...] > * Temporary memory that is located after the value of the free memory pointer at the beginning of the assembly block, i.e. memory that is “allocated” at the free memory pointer **without updating the free memory pointer**. > > [...] > > On the other hand, **the following code _is_ memory safe**, because memory beyond the location pointed to by the free memory pointer can safely be used as temporary scratch space: > > ```solidity > assembly ("memory-safe") { > let p := mload(0x40) > returndatacopy(p, 0, returndatasize()) > revert(p, returndatasize()) > } > ``` I also [did some investigation](safe-global/safe-smart-account#544 (comment)) and found that when variables are moved into memory, the space gets reserved **before** user code starts, meaning that the free memory pointer already accounts for the reserved space (i.e. setting variables cannot write past the free memory pointer ever). With that in mind, we do not need to update the free memory pointer when we write past it for scratch space. cc @mmv08
Description
Hi, we recently find an issue when compiling
safe-contracts
in our test suite using the latest version of the Solidity compiler with theyul
optimizer enabled. You can read more details and discussions about the issue here: ethereum/solidity#14082In summary, it throws
stack too deep
error (see below) when using the following compiler settings:{"viaIR":true,"optimizer":{"enabled":true, "details": {"yul": true}}}
The problem was introduced by this commit: b9fdbde
Environment
0.8.19
evmVersion: 'paris', viaIR: true, optimizer: {enabled: true, details: {yul: true}}}
Steps to Reproduce
Set the
.env
to:And run:
yarn build
. This will produce the following error:Possible solution
Add the "memory-safe" annotation to assembly code blocks when needed.
The text was updated successfully, but these errors were encountered: