runloop: re-fetch the menu handle after menu_driver_iterate()

Audit follow-up to 38234ab96d (menu: re-fetch the menu handle after
menu_entry_action()): the same invalidation reaches
runloop_check_state(). The menu handle is cached at function entry,
menu_driver_iterate() runs entry actions that can free and recreate
it, and the render block that follows reads and writes the stale
pointer - menu->state bit updates, driver_ctx->render_messagebox /
render calls, and menu->userdata handed to the old driver. On 64-bit
heaps this lands in freed memory silently on every
menu-reinit-via-action (e.g. changing the menu driver from the menu)
rather than faulting.

The existing re-fetch after display_menu_libretro() (e03f25c569)
covers core_run() invalidating the handle, but sits below this
render block; add the matching re-fetch above it. Both are needed:
iterate and core_run are separate invalidation points with uses
between them.

Found by auditing every menu_st->driver_data cache that survives
across a command_event/action dispatch; the remaining flagged sites
were verified safe (uses precede the dispatch, cross-case switch
noise, or commands that cannot reach RARCH_MENU_CTL_DEINIT).
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-08-30 16:25:27 +02:00
1 parent 38234ab96d
commit 6929c66ec9
1 file changed
+10
+10
View File
@@ -6947,6 +6947,16 @@ static enum runloop_state_enum runloop_check_state(
menu_pause_libretro);
#endif
/* menu_driver_iterate() above dispatches entry actions, which
* can tear down and recreate the menu handle (menu driver
* change, any CMD_EVENT_REINIT from an action) - the pointer
* cached at the top of this function is stale from here on.
* Re-fetch before the render block below reads and writes
* through it; the second re-fetch further down covers
* core_run() inside display_menu_libretro() invalidating it
* again. */
menu = menu_st->driver_data;
if (menu)
{
if (BIT64_GET(menu->state, MENU_STATE_RENDER_FRAMEBUFFER)