Skip to content
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

[Codegen] Recursive fallback function call runs out of gas via legacy but does not via IR #13216

Open
bshastry opened this issue Jun 26, 2022 · 13 comments
Labels
bug 🐛 codegen error Compiler generates invalid code. Critical. high impact Changes are very prominent and affect users or the project in a major way. needs investigation viair

Comments

@bshastry
Copy link
Contributor

contract C0 {
  fallback() external virtual
  {
    assembly
    {
      calldatacopy(0, not(0), 96)
    }
    (bool l0, bytes memory l1) = address(this).call(bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000"));
  }
}
// ====
// compileViaYul: also
// ----
// () -> FAILURE
// gas irOptimized: 211360

Legacy: FAILURE
IR: Pass

@ekpyron ekpyron added the codegen error Compiler generates invalid code. Critical. label Aug 12, 2022
@ekpyron
Copy link
Member

ekpyron commented Aug 12, 2022

I'm labeling this codegen error, to heighten the priority of properly investigating this, even though I still hope, it's mostly a testing issue.

@ekpyron ekpyron added testing 🔨 codegen error Compiler generates invalid code. Critical. and removed codegen error Compiler generates invalid code. Critical. testing 🔨 labels Aug 12, 2022
@ekpyron
Copy link
Member

ekpyron commented Aug 15, 2022

I think this may be spurious after all - and just a crazy thing happening here :-).
I've not confirmed entirely yet, but I think
calldatacopy(0, not(0), 96) can be replaced with anything that ends up having the same gas cost.

What I guess will happen is that the last recursive call to this may just report failure, but the contract may still have enough gas to finish returning, depending on legacy vs via-IR. But we have to confirm that everything is sane there wrt. gas forwarding, etc.
It's surprising in any case :-).

@ekpyron
Copy link
Member

ekpyron commented Aug 15, 2022

Or to be even clearer: the maximum forwarded gas is 63/64 of the available gas.
So if the recursive call runs out of gas, we still have 1/64 of the total gas left. This may just be enough for finishing the transaction in via-IR codegen, while it may not be with legacy codegen.

@ekpyron
Copy link
Member

ekpyron commented Aug 15, 2022

Several of the recursive calls may manage to return with the available gas - when I added some event to the code example (which of course skews gas costs, so it won't be exactly the same), I saw somewhere between 100 and 200 calls succeeding.

@bshastry
Copy link
Contributor Author

@ekpyron Isn't calldatacopy(0, not(0), 96) in this issue's test case also overwriting the free memory pointer. If so, could this also be undefined behavior between legacy and IR?

@ekpyron
Copy link
Member

ekpyron commented Aug 15, 2022

That's true... although my guess would be that one could construct similar cases without that... on the other hand, trying that, it's not that easy :-). Overwriting the free memory pointer there, may mainly result in weird data to be passed during the recursive call, though.

@ekpyron
Copy link
Member

ekpyron commented Aug 15, 2022

Hm... or maybe I was wrong before and there's something else happening here... maybe worth another look in any case :-).

@bshastry
Copy link
Contributor Author

Okay, in the meantime I will work on avoiding writes to free memory ptr while generating test cases. For the time being, I could template all memory writes to start at 0x60 i.e., mstore(add(loc, 0x60), value), calldatacopy(add(loc, 0x60), from, size) and so on; where loc is a randomly generated value.

@ekpyron
Copy link
Member

ekpyron commented Aug 15, 2022

Make it 0x80 - offset 0x60 is the "zero-memory offset", which is used to initialize zero-length dynamic arrays and always has to remain zero - i.e. writing anything non-zero there will also be undefined :-).

What's strictly speaking always well-defined is would be

mstore(add(mload(0x40), loc), ...)

(given that loc is bounded and the addition does not overflow - or more conservatively add(mload(0x40), and(loc, 0xffffffff)))

And

mstore(and(loc, 0x3f), ...)

But we also need to make sure not to restrict this too much in order not to miss anything...

@bshastry
Copy link
Contributor Author

mstore(add(mload(0x40), loc), ...)

This would be load from free memory pointer and add an arbitrary bounded value to it, correct? Assuming loc is bounded this should be fine.

mstore(and(loc, 0x3f), ...)

What does this mean? 0x3f would make only 6 addressable bits, right? That would be a lot more conservative than first proposal?

@ekpyron
Copy link
Member

ekpyron commented Aug 15, 2022

I meant choosing between either of them. And actually and(loc, 0x3f) was wrong... 0x1f would work, though...
You're allowed to use any memory past the free memory pointer and any memory between offsets 0 and 0x3f.
mstore(add(mload(0x40), loc), ...) is the former, mstore(and(loc, 0x1f), ...) at least roughly the latter.

But in any case, I don't think we should fully restrict to those cases, i.e. your suggestion of using a fixed offset may be better.

We want the fuzzer to occasionally dirty previously allocated memory after all.
So maybe alternating between mstore(add(0x80, loc), ...) (with bounded loc) and mstore(loc, ...) (with loc being restricted to at most 0x20) is best.

@NunoFilipeSantos
Copy link
Contributor

@bshastry what is the impact of this bug (High, Medium, Low)?

@bshastry
Copy link
Contributor Author

I would say low if at all. There is the possibility that this is not a bug because overwriting the free memory pointer is essentially undefined behavior.

@NunoFilipeSantos NunoFilipeSantos added the low impact Changes are not very noticeable or potential benefits are limited. label Oct 28, 2022
@NunoFilipeSantos NunoFilipeSantos added high impact Changes are very prominent and affect users or the project in a major way. and removed low impact Changes are not very noticeable or potential benefits are limited. labels Nov 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 codegen error Compiler generates invalid code. Critical. high impact Changes are very prominent and affect users or the project in a major way. needs investigation viair
Projects
None yet
Development

No branches or pull requests

3 participants