Two egg hazards closed, and a heap overrun still to find

-pb died with exit 0xC0000374, heap corruption, nothing in the log. Two
causes found, one fixed hazard behind them, and the crash itself still
open.

The first cause was mine. L4NetworkManager has always dumped whatever egg
it loaded to "last.egg" as a debug aid, and the egg-beside-the-spool work
put a recording's companion egg at exactly that name. Playback's fallback
then picked up an unrelated egg as though it belonged to last.spl. That
matters more than a wrong filename: the spool header holds one
(remote, hostID) pair per host named in the EGG, so an egg with a
different host count makes the reader consume the wrong number of pairs,
leave the read pointer mid-header, and parse the first packet out of
garbage. The debug dump is now last-loaded.egg.

The second is that nothing checked. A wrong egg could only announce itself
by corrupting memory, which is the least useful signal a program can give.
Playback now looks at the first packet after the host table and refuses a
length that cannot be right, naming the cause: play it back with the .egg
saved beside it.

Neither fixed the crash. With the guard in place and an egg it accepts,
playback still corrupts the heap - and the guard did NOT fire, so the
header was consumed consistently and the fault is later, in dispatching
the packets themselves. The stack at the failure is inside ntdll's
allocator, which is where corruption is DETECTED rather than where it is
caused, so the next step is page heap to find the write rather than more
staring.

Where MR stands after today: it loads the world from the egg, lays the
cockpit out as whatever the station was, accepts the spool's header, and
gets as far as playing packets into the simulation. That is a good deal
further than "crashes on the first thing it touches" this morning, and the
remaining fault is a single memory overrun in a code path that has not run
since 2007.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-11 14:30:24 -05:00
co-authored by Claude Opus 5
parent ef81b75058
commit 48a595b244
2 changed files with 42 additions and 1 deletions
+9 -1
View File
@@ -214,7 +214,15 @@ L4NetworkManager::L4NetworkManager():
}
networkEggNotationFile = new NotationFile(egg_name);
Register_Object(networkEggNotationFile);
networkEggNotationFile->WriteFile("last.egg");
//
// NOT last.egg. That name now belongs to the egg saved beside
// last.spl for playback, and this is something else entirely - a
// debug dump of whatever egg was just loaded. The two collided, and
// playback picking this one up as if it were a recording's companion
// read a host table of the wrong length out of the spool header,
// walked off the end of it, and corrupted the heap.
//
networkEggNotationFile->WriteFile("last-loaded.egg");
currentNetworkState = NormalState;
ReceiveEggFileMessage egg_message(-1, 10, "local egg", 10);
+33
View File
@@ -367,6 +367,39 @@ void
}
}
//
// Did the header end where the packets begin?
//
// The table above holds one pair per host named in the EGG, and the
// reader trusts the egg to have as many hosts as the spool was written
// with. Hand it a different egg and the count differs, the read pointer
// stops short of - or past - the first packet, and every packet after
// that is parsed from the middle of something else. The first symptom is
// a nonsense message length, and the second is a corrupted heap: exit
// 0xC0000374, no message, nothing in the log.
//
// A packet here should start with a plausible length. If it does not,
// the egg does not belong to this spool, and saying so is worth more
// than whatever the heap does next.
//
{
NetworkPacket *first = (NetworkPacket*) spool->GetPointer();
int first_length = (int) first->messageData.messageLength;
if (first_length < (int) sizeof(Receiver::Message)
|| first_length > 65536
|| first_length > (int) spool->GetBytesRemaining())
{
DEBUG_STREAM << "\n\nError - this egg does not belong to this spool."
<< " The host table ran to the wrong length and the first packet"
<< " reads as " << first_length << " bytes, which cannot be"
<< " right. Play it back with the .egg that was saved beside"
<< " it.\n" << std::flush;
PostQuitMessage(AbortExitCodeID);
return;
}
}
//
// Now, just send the load message
//