MemoryUtil: fall back to far memory when the near reservation fails

The x86-64 path searched for free memory near the code, and if it found some,
committed to it - if that VirtualAlloc failed, ptr was left null and we returned
null, never reaching the else branch that exists precisely to say "can still run,
thanks to RipAccessible".

Finding a free region isn't the same as being able to reserve it. VirtualAlloc
rounds a non-null lpAddress down to the 64K allocation granularity while
SearchForFreeMem only guarantees page alignment, so the rounded-down base can land
back inside a committed region; a concurrent allocation between the VirtualQuery and
the VirtualAlloc does it too. Callers don't check the result - AllocCodeSpace stores
it unchecked and the emitters write from there - so this turned into a wild write
rather than a clean JIT-unavailable fallback.
This commit is contained in:
Henrik Rydgård
2026-08-31 12:52:35 +02:00
parent cf8ef68f6b
commit f094752f21
+9
View File
@@ -145,8 +145,17 @@ void *AllocateExecutableMemory(size_t size) {
#endif
if (ptr) {
ptr = VirtualAlloc(ptr, aligned_size, MEM_RESERVE | MEM_COMMIT, prot);
if (!ptr) {
// Finding a free region isn't the same as being able to reserve it: VirtualAlloc
// rounds a non-null address down to the 64K allocation granularity, and
// SearchForFreeMem only returns page alignment, so the rounded-down base can land
// back inside something committed. Another thread allocating in between does it too.
WARN_LOG(Log::Common, "Could not reserve the nearby executable memory found for jit. Proceeding with far memory.");
}
} else {
WARN_LOG(Log::Common, "Unable to find nearby executable memory for jit. Proceeding with far memory.");
}
if (!ptr) {
// Can still run, thanks to "RipAccessible".
ptr = VirtualAlloc(nullptr, aligned_size, MEM_RESERVE | MEM_COMMIT, prot);
}