From e51379766ddf692b640316d48a86642589766d2b Mon Sep 17 00:00:00 2001
From: Jan Rydzewski <SupraSummus@users.noreply.github.com>
Date: Thu, 7 May 2026 22:01:15 +0000
Subject: [PATCH] simworld: wrap karte_t::step body with
 intr_disable/intr_enable

INT_CHECK calls inside karte_t::step (during fab_list iteration in
simfab.cc, and several explicit INT_CHECK("...") sites in simworld.cc
itself) can pump GUI events while step is mid-iteration over
fab_list / convoi_array / cities. If a welt_gui_t ("New world")
dialog is open, its "Start" handler in gui/welt.cc calls
welt->init(), which runs karte_t::destroy() and frees the very
factory whose step is on the stack. ASAN reports a
heap-use-after-free in array_tpl<ware_production_t>::get_count.

The karte_t::interactive() loop calls step() with no wrap; only
modal_dialogue() in gui/simwin.cc currently guards the call with
intr_disable()/intr_enable(). Pushing the wrap into step() itself
makes the guard apply to every caller and renders the
modal_dialogue wrap redundant (harmless, can be removed in a
follow-up if desired).
---
 src/simutrans/world/simworld.cc | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/simutrans/world/simworld.cc b/src/simutrans/world/simworld.cc
index a19d9ed99..95862f04e 100644
--- a/src/simutrans/world/simworld.cc
+++ b/src/simutrans/world/simworld.cc
@@ -3226,6 +3226,9 @@ void karte_t::step()
 	last_step_ticks = ticks;
 	steps ++;
 
+	// Block re-entrant GUI dispatch — INT_CHECK below would otherwise let welt_gui_t's "Start" button call welt->init() and free what we're iterating. Mirrors modal_dialogue (gui/simwin.cc).
+	intr_disable();
+
 	// to make sure the tick counter will be updated
 	INT_CHECK("karte_t::step");
 
@@ -3345,6 +3348,8 @@ void karte_t::step()
 		}
 	}
 
+	intr_enable();
+
 	DBG_DEBUG4("karte_t::step", "end");
 }
 
