From 8e37a087556bca4eb7a5d7436639a17e156827a5 Mon Sep 17 00:00:00 2001
From: Claude <noreply@anthropic.com>
Date: Sun, 31 May 2026 22:56:51 +0000
Subject: [PATCH] network: normalise empty tool default_param at the wire
 boundary

A network client controls the nwc_tool_t default_param, and a zero-length
wire string deserialises to a NULL plainstring (rdwr_str leaves the buffer
NULL for len==0). The single-player tool handlers assume the GUI always
supplies a non-NULL default_param, so the empty param reaches atoi(NULL) /
*p derefs across the change_*/rename/recolour tool inits and the
way/roadsign/building name lookups.

Trace that NULL to its one origin and plug it there: normalise it to "" in
nwc_tool_t::rdwr, the single point where network-sourced tool params enter
the tool subsystem. This subsumes the per-tool NULL guards. The wire can
already not distinguish an empty param from "no param", so collapsing
empty->"" on receive loses no information.

Out of scope (distinct bug classes a boundary normalisation cannot reach):
the lookup-returned-NULL derefs (tool_build_depot_t::get_waytype,
tool_transformer_t::work) crash for an unknown name / off-map tile
regardless of input; and tool_change_line_t::init still walks one byte past
the terminator on an empty/all-whitespace param (a parser bug, not a NULL).
---
 src/simutrans/network/network_cmd_ingame.cc | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/simutrans/network/network_cmd_ingame.cc b/src/simutrans/network/network_cmd_ingame.cc
index 044a0da62..8905171de 100644
--- a/src/simutrans/network/network_cmd_ingame.cc
+++ b/src/simutrans/network/network_cmd_ingame.cc
@@ -1117,6 +1117,14 @@ void nwc_tool_t::rdwr()
 	packet->rdwr_short(tool_id);
 	packet->rdwr_short(wt);
 	packet->rdwr_str(default_param);
+	if(  packet->is_loading()  &&  default_param == NULL  ) {
+		// An empty wire string deserialises to a NULL plainstring (rdwr_str
+		// leaves the buffer NULL for len==0). The tool handlers assume the
+		// non-NULL default_param the GUI always supplies, so normalise it
+		// here -- the one point where network tool params enter the tool
+		// subsystem -- instead of guarding atoi(NULL)/*p in every tool.
+		default_param = "";
+	}
 	packet->rdwr_bool(init);
 	packet->rdwr_long(tool_client_id);
 	packet->rdwr_byte(flags);
