<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://manicpop.org/gewiki/index.php?action=history&amp;feed=atom&amp;title=Classic_bug%3A_Planet_trade-password_terminator_overrun</id>
	<title>Classic bug: Planet trade-password terminator overrun - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://manicpop.org/gewiki/index.php?action=history&amp;feed=atom&amp;title=Classic_bug%3A_Planet_trade-password_terminator_overrun"/>
	<link rel="alternate" type="text/html" href="https://manicpop.org/gewiki/index.php?title=Classic_bug:_Planet_trade-password_terminator_overrun&amp;action=history"/>
	<updated>2026-10-20T16:32:27Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.46.2</generator>
	<entry>
		<id>https://manicpop.org/gewiki/index.php?title=Classic_bug:_Planet_trade-password_terminator_overrun&amp;diff=1568&amp;oldid=prev</id>
		<title>CodexBot: Link first command references to command pages</title>
		<link rel="alternate" type="text/html" href="https://manicpop.org/gewiki/index.php?title=Classic_bug:_Planet_trade-password_terminator_overrun&amp;diff=1568&amp;oldid=prev"/>
		<updated>2026-10-12T02:15:15Z</updated>

		<summary type="html">&lt;p&gt;Link first command references to command pages&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;{{Classic bug&lt;br /&gt;
|id=GE-BUG-038&lt;br /&gt;
|affected=Classic 3.2e and earlier versions derived from the same source&lt;br /&gt;
|status=Verified in source; corrected in maintained descendants&lt;br /&gt;
|fixed=Elwynor&amp;#039;s 32-bit port; ge-next 0.3&lt;br /&gt;
|source=Planet trade-password handling in &amp;lt;code&amp;gt;mnu_admenu2i()&amp;lt;/code&amp;gt;; &amp;lt;code&amp;gt;GALPLNT.password&amp;lt;/code&amp;gt; layout&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Summary ==&lt;br /&gt;
Classic GE writes the terminating null byte for a planet trade password one byte beyond the end of its &amp;lt;code&amp;gt;password[10]&amp;lt;/code&amp;gt; array. In the classic planet record layout, that byte is the first byte of the immediately following &amp;lt;code&amp;gt;lastattack&amp;lt;/code&amp;gt; field.&lt;br /&gt;
&lt;br /&gt;
The routine also copies only nine password characters and leaves &amp;lt;code&amp;gt;password[9]&amp;lt;/code&amp;gt; untouched. Nine-character or longer input is therefore terminated correctly only when that old byte already happens to contain zero.&lt;br /&gt;
&lt;br /&gt;
The adjacent field appears to be an abandoned feature and is not read by any published classic 3.2e routine. That limits the visible consequences in a normal game, but the operation is still an out-of-bounds write that is saved back into the planet database.&lt;br /&gt;
&lt;br /&gt;
== Planet record layout ==&lt;br /&gt;
Classic stores each planet in a fixed 512-byte &amp;lt;code&amp;gt;GALPLNT&amp;lt;/code&amp;gt; record. The relevant fields are consecutive:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char warnings;&lt;br /&gt;
char password[10];&lt;br /&gt;
char lastattack[UIDSIZ];&lt;br /&gt;
ITEM items[NUMITEMS];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the 16-bit record layout, &amp;lt;code&amp;gt;password&amp;lt;/code&amp;gt; occupies byte offsets 91 through 100. &amp;lt;code&amp;gt;lastattack&amp;lt;/code&amp;gt; begins at offset 101. Character arrays do not require alignment padding between them, so &amp;lt;code&amp;gt;password[10]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;lastattack[0]&amp;lt;/code&amp;gt; designate the same stored byte in the expected layout:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
offset  91 ... 100  101 ... 130&lt;br /&gt;
field   password     lastattack&lt;br /&gt;
index   [0] ... [9]  [0] ... [29]&lt;br /&gt;
                     ^&lt;br /&gt;
                     password[10] writes here&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An index equal to an array&amp;#039;s declared length is outside that array. The final valid element of &amp;lt;code&amp;gt;password[10]&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;password[9]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Faulty password assignment ==&lt;br /&gt;
After the planet owner selects the trade-password option in [[Admin command|&amp;lt;code&amp;gt;ADMIN&amp;lt;/code&amp;gt;]], &amp;lt;code&amp;gt;mnu_admenu2i()&amp;lt;/code&amp;gt; executes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
strncpy(plptr-&amp;gt;password, margv[0], 9);&lt;br /&gt;
plptr-&amp;gt;password[10] = 0;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These two constants do not describe one coherent buffer operation:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;strncpy(..., 9)&amp;lt;/code&amp;gt; writes only elements 0 through 8.&lt;br /&gt;
* Element 9 is not explicitly changed by either statement.&lt;br /&gt;
* Element 10 is outside the array and receives the null terminator instead.&lt;br /&gt;
&lt;br /&gt;
For input shorter than nine characters, &amp;lt;code&amp;gt;strncpy()&amp;lt;/code&amp;gt; pads the copied range with null bytes, so the resulting password is terminated within elements 0 through 8. For input nine characters or longer, all nine copied bytes are nonzero and the routine depends on the pre-existing value in element 9.&lt;br /&gt;
&lt;br /&gt;
If &amp;lt;code&amp;gt;password[9]&amp;lt;/code&amp;gt; is zero, the apparent password is the expected nine-character truncation. This is common because newly created planet records are cleared before receiving the default password &amp;lt;code&amp;gt;none&amp;lt;/code&amp;gt;. It is not, however, guaranteed by this assignment.&lt;br /&gt;
&lt;br /&gt;
If &amp;lt;code&amp;gt;password[9]&amp;lt;/code&amp;gt; contains a nonzero byte, string operations continue through it and stop at the zero incorrectly written into &amp;lt;code&amp;gt;lastattack[0]&amp;lt;/code&amp;gt;. The effective password then consists of the nine new characters followed by one stale character.&lt;br /&gt;
&lt;br /&gt;
== Adjacent-field corruption ==&lt;br /&gt;
The out-of-bounds store clears &amp;lt;code&amp;gt;lastattack[0]&amp;lt;/code&amp;gt;. A conventional null-terminated string whose first byte is zero is empty, so any user ID previously stored there would be erased when the planet owner changed the trade password. The modified planet record is then written to the database, making the corruption persistent.&lt;br /&gt;
&lt;br /&gt;
Despite its name and comment, &amp;lt;code&amp;gt;lastattack&amp;lt;/code&amp;gt; is not part of a working classic feature. In the published 3.2e source it is initialized when a planet is created or reset, but no routine records an attacker there or reads it for gameplay. Consequently, clearing it does not normally change combat, ownership, or notifications. The bug is more likely to appear as a malformed full-length password than as a visible failure involving the adjacent field.&lt;br /&gt;
&lt;br /&gt;
This limited consequence is accidental. A change to the structure or an implementation that made use of &amp;lt;code&amp;gt;lastattack&amp;lt;/code&amp;gt; would turn the same off-by-one store into active state corruption.&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
Assume the ten password bytes contain the following before the owner enters a new value. The old password is still the valid string &amp;lt;code&amp;gt;none&amp;lt;/code&amp;gt;, but byte 9 contains a stale &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; after its earlier terminator:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
index:      0 1 2 3 4 5 6 7 8 9&lt;br /&gt;
value:      n o n e \0 \0 \0 \0 \0 X&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The owner enters &amp;lt;code&amp;gt;marketkey&amp;lt;/code&amp;gt;, which contains nine characters. Classic copies those characters into indexes 0 through 8 but does not change the old &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; at index 9:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
password:   marketkeyX&lt;br /&gt;
index:      0123456789&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It then writes a zero to index 10, actually clearing &amp;lt;code&amp;gt;lastattack[0]&amp;lt;/code&amp;gt;. Functions that display or compare the password see &amp;lt;code&amp;gt;marketkeyX&amp;lt;/code&amp;gt;, not &amp;lt;code&amp;gt;marketkey&amp;lt;/code&amp;gt;. A player supplying the nine-character value selected by the owner can therefore be refused trading or maintenance access.&lt;br /&gt;
&lt;br /&gt;
Fresh classic records normally have zero in index 9, which is why this symptom is not inevitable. The example shows what the code permits when that byte is nonzero, including in edited, migrated, or previously corrupted records.&lt;br /&gt;
&lt;br /&gt;
== Corrections ==&lt;br /&gt;
Elwynor&amp;#039;s 32-bit classic port retains &amp;lt;code&amp;gt;password[10]&amp;lt;/code&amp;gt; and the original nine-character limit, but corrects the terminator index:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
strncpy(plptr-&amp;gt;password, margv[0], 9);&lt;br /&gt;
plptr-&amp;gt;password[9] = &amp;#039;\0&amp;#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This guarantees termination without changing the classic record layout.&lt;br /&gt;
&lt;br /&gt;
The initial ge-next correction, made during 0.3 development, expanded the field to &amp;lt;code&amp;gt;password[11]&amp;lt;/code&amp;gt;, allowing ten password characters plus a terminator. At that stage it replaced the unused remainder of the old &amp;lt;code&amp;gt;lastattack&amp;lt;/code&amp;gt; field with temporary padding. This preserved the offsets of every following field in the then-current structure:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char password[11];&lt;br /&gt;
char filler2[UIDSIZ - 1];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That was an intermediate compatibility step, not the final ge-next 0.3 record design. The later structure reorganization removed the unused &amp;lt;code&amp;gt;warnings&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;lastattack&amp;lt;/code&amp;gt; fields, removed &amp;lt;code&amp;gt;filler2&amp;lt;/code&amp;gt;, and placed the item array directly after the enlarged password:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int taxrate;&lt;br /&gt;
char password[11];&lt;br /&gt;
ITEM items[NUMITEMS];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The reorganization also corrected the structures to their real database record sizes and consolidated unused space into padding at the end. The current 16-bit &amp;lt;code&amp;gt;GALPLNT&amp;lt;/code&amp;gt; is the actual 493-byte MajorBBS record; its password begins at offset 90 and its item array at offset 101. ge-next32 uses the same logical field order, with the alignment required by its platform and padding to its 512-byte MajorBBS V10 record.&lt;br /&gt;
&lt;br /&gt;
The assignment derives both the copy length and terminator position from the actual destination size:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
strncpy(plptr-&amp;gt;password, margv[0], sizeof(plptr-&amp;gt;password) - 1);&lt;br /&gt;
plptr-&amp;gt;password[sizeof(plptr-&amp;gt;password) - 1] = 0;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This removes the mismatched magic numbers, always terminates the string, and supports the same ten-character maximum used for team passwords. ge-next32 uses the same corrected assignment and the restructured field layout. These changes were all completed before ge-next 0.3 was released; that release&amp;#039;s larger database redesign required a new game rather than conversion of classic planet records.&lt;br /&gt;
&lt;br /&gt;
[[Category:Classic bugs: Memory]]&lt;br /&gt;
[[Category:Classic bugs: Planets]]&lt;br /&gt;
&lt;br /&gt;
{{GE navigation}}&lt;/div&gt;</summary>
		<author><name>CodexBot</name></author>
	</entry>
</feed>