Non memory-safe assembly

Reviewing some code I several times see memory-unsafe assembly that is marked as “memory-safe”. In particular code that temporarily overwrites the free pointer and restores it at the end of the block, or that overwrites it and then immediately reverts.

From the documentation I gather that this is not memory-safe and declaring it as memory-safe may lead to problems. In the examples I saw it didn’t, but maybe they just got lucky? Is the danger just that the next version of the compiler may move more things around, or can it already happen with some optimization settings?

Would you recommend to just remove the “memory-safe” or would it be better (maybe even more gas efficient) to fix the assembly code and use the space starting at the free memory pointer as scratch space instead?

Hello @jhoenicke! Thanks for the question.

Overwriting free memory pointer is safe as long as it is restored at the end of the block. I believe that the following section in the docs mentions the same as memory safe:
https://docs.soliditylang.org/en/stable/assembly.html#memory-safety

Could you please clarify as to why you think this shouldn’t be declared as memory safe?

Do the docs say that :-)? I’d be surprised if they did and I don’t see anything of the sort there.

If I understand the cases this post is about correctly, technically, they’re not memory-safe. Memory-safe accesses are only accesses to the scratch space at 0-64 and space beyond the current value of the free memory pointer - that doesn’t include the free memory pointer itself (at offset 64), no matter whether it is restored or not.

So much for memory-safety by definition in the docs.

De-facto, at least currently, nothing bad will happen if you also use the free memory pointer itself as scratch space, as long as you restore it or terminate. Properly unsafe use that can in fact cause issues currently is only code that moves further and overwrites memory starting at 0x80 - both 0x40 and 0x60 are fine as long as you restore the original values before going back to Solidity (or you don’t go back to Solidity). We’re unlikely to change this, but we could and wouldn’t have to consider that breaking. Note that if you’re using memory beyond 0x80 and don’t run into issues, then you’re merely lucky - no matter whether you restore things or terminate.

That being said, there is no guarantee for this behaviour and I’d indeed recommend using the scratch space at the free memory pointer instead, if you need more than 64 bytes.

Thanks for the replies. I also heard the opinion that overwriting memory pointer is okay, because there is even an example that overwrites the free memory pointer to allocate memory, but overwriting the zero pointer at 0x60 is not memory safe and may even currently break (although I haven’t seen an example where this happens). But as I get from ekpyron’s reply, everyone relying on this is risking problems with a later solidity release, even though they will probably be fine.

I’ve also seen code where the memory pointer is partly overwritten in the most significant bytes without saving it and then restored by clearing these bytes (mstore(0x34, data) followed later by mstore(0x34,0). I guess if you overwrite only the first 28 bytes of the free memory pointer, you can be quite sure they were zero anyway, at least with the gas model of ethereum. But this feels even less safe.

Yeah, using the free memory pointer for allocating memory according to Solidity’s memory model is definitely valid, that’s indeed even in the docs in the form of the allocate function.
Now the compiler will never itself inject new allocations in the middle of an assembly block - the compiler only relies on it having “sane” values for generating code for high-level Solidity.
So yeah, by that argument it’s not merely unlikely for us to break this, but we really couldn’t. Maybe we should actually even officialize that and change the docs to declare this explicitly as safe (and thus strongly guarantee that this won’t break).

I would certainly hope that if Solidity ever starts managing memory inside of an assembly block without an explicit memory instruction that it is considered/documented as a breaking change.

IMO as long as you restore the memory state within the block you’re still respecting the memory model.

1 Like

I actually just created Relax the definition of memory safety in the documentation. by ekpyron · Pull Request #15238 · ethereum/solidity · GitHub to explicitly document this as safe - we’ll discuss this internally, so don’t take it for granted until we merge it, but I think it likely that we will at least properly guarantee this for the free memory pointer at 0x40, potentially also for the zero value at 0x60.

1 Like