Files
LibretroAdmin 9b89193fd8 GS: an erase from an empty FastList wrote 128 KB past the buffer
The crash, from the Dr. MinGW trace: heap corruption (0xC0000374)
reported by free, inside ~Source under InvalidateTemporarySource, under
CleanupDraw - Tomb Raider Legend, scope view, and it happens on Vulkan
as well as D3D, so it is not a backend. A heap-corruption status at free
means the allocator found its bookkeeping damaged; the damage was done
earlier, by something else, and this free is only where it was noticed.

FastList::EraseIndex is the something else:

	m_free_indexes_stack[--m_free_indexes_stack_top] = index;

m_free_indexes_stack_top is a u16 and is zero when the list holds
nothing, so the pre-decrement takes it to 65535 and the store lands 128
KB past the buffer, in whatever the allocator has there. ListRemove on
the line above has already written two more through a stale index.
Nothing fails at that moment. The next free of a block whose header the
store landed on is where it surfaces, and that free can be anywhere -
here it was a Source being deleted at the end of a draw.

Getting there needs a stale index, which the texture cache hands out:
SourceMap keeps one m_erase_it per page in every Source, RemoveAt hands
them all back later, and anything that emptied those lists in between
makes every one of them stale. A resolution change purges the texture
cache, which is what the report started with; the scope view is a
channel shuffle, which is the path that creates and drops sources
hardest.

EraseIndex and MoveFront now ignore an index that cannot be theirs - no
buffer, index zero, past the capacity, or nothing held. Build with
-DFASTLIST_CHECKS=1 to have them say so, with the list, the index and
the count, which is how to find the caller that is still handing over a
stale one. That caller is a bug of its own and this does not fix it; it
stops it corrupting the heap.

tests/fastlist covers it: erase and move on a cleared list, on an
allocated-but-empty one, index zero, and an out-of-range index, then
checks the list still works. Without the guard that test is an
AddressSanitizer report; with it the suite passes plain and under
ASan/UBSan.

The guard is worth having whatever the list's allocation strategy: the
same store ran off the end of the four-element buffer before lists
allocated lazily (3bf33f90e), 128 KB past it either way.
2026-09-20 01:23:38 +02:00
..