code-423n4 / 2024-07-optimism-findings

3 stars 0 forks source link

`MIPS` - Incorrect handling of memory mapping #37

Closed c4-bot-3 closed 2 months ago

c4-bot-3 commented 2 months ago

Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/MIPS.sol#L176-L179

Vulnerability details

Impact

Incorrect resolution of dispute game - True root claim is resolved as False

Proof of Concept

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

The above code-snippet shows an interface of low-level mmap function.

The first parameter is a preferred addr for mapping the memory, but when it is set to NULL(which is most of the case), the address will be determine in run-time, based on different factors like:

However, in MIPS, it is assumed that newly mapped memory always exists at the last of the heap:

if (a0 == 0) {
    v0 = state.heap;
    state.heap += sz;
} else {
    v0 = a0;
}

mmap does not guarantee that mapped memory exists in heap, it can be anywhere in virtual memory. Also based on above facts, the resulting $v0 might have different value between actual run-time and in MIPS, which will result in incorrect game resolution.

Tools Used

Manual Review

Recommended Mitigation Steps

Mitigation is not 100% clear because it depends on run-time how memory is managed.

Assessed type

Context

Inphi commented 2 months ago

This report is valid. It's possible for a program to retrieve an anonymous mapping from the VM if it had previously retrieved a hinted mapping.

However, this won't result to a faulty fault proof specifically for the op-program due to the way it uses mmap. The op-program via the Go runtime, reserves non-anonymous memory at startup for main heap arena. The arena retrieves memory from the VM via non-anonymous calls, however these occur at the end of the arena. So effectively bump allocation. For anonymous mappings, this is also requested by the Go runtime for its small object heap. Since the runtime knows exactly how large the various heaps are and their placement, it is able to assert that neither mappings overlap. And it does (see the various checks here whenever the mutator allocates).

It is possible for new heap arenas to overlap the small object heap. But this occurs only when the op-program uses alot of memory (just over a GiB according to our tests). In that event, the Go runtime is able to detect this and crashes the program. So this issue can only manifest if the op-program somehow required a lot of memory (i.e. memory that isn't garbage collected) for a derivation run.

c4-judge commented 2 months ago

zobront marked the issue as unsatisfactory: Invalid

KupiaSecAdmin commented 2 months ago

@Inphi - When objects are dynamically allocated in heap using mmap and later some of them might be freed by Garbage Collector, which makes free spans and spaces in the heap. Later when there's new memory being allocated, the address to freed spans will be returned as address instead of just allocating at the end of heap.

Inphi commented 1 month ago

@KupiaSecAdmin The Cannon ELF loader disables garbage collection for the op-program. Thus no memory is freed, not even via a gc assist, throughout the execution of the op-program.