Prevent buffer overflows when using streams

This commit is contained in:
Chris Cowan 2025-06-27 19:27:22 -07:00 committed by Ricardo Luís Vaz Silva
commit ab7b1642bc
18 changed files with 600 additions and 388 deletions

View file

@ -178,8 +178,8 @@ bool demorecording;
bool demoplayback;
bool demonew; // [RH] Only used around G_InitNew for demos
int demover;
uint8_t* demobuffer;
uint8_t* demo_p;
TArray<uint8_t> demobuffer;
TArrayView<uint8_t> demo_p;
uint8_t* democompspot;
uint8_t* demobodyspot;
size_t maxdemosize;
@ -2089,8 +2089,8 @@ void G_DoLoadGame ()
arc("importantcvars", cvar);
if (!cvar.IsEmpty())
{
uint8_t *vars_p = (uint8_t *)cvar.GetChars();
C_ReadCVars(&vars_p);
auto vars_p = cvar.GetTArrayView();
C_ReadCVars(vars_p);
}
else
{
@ -2488,14 +2488,14 @@ void G_ReadDemoTiccmd (usercmd_t *cmd, int player)
while (id != DEM_USERCMD && id != DEM_EMPTYUSERCMD)
{
if (!demorecording && demo_p >= zdembodyend)
if (!demorecording && demo_p.Data() >= zdembodyend)
{
// nothing left in the BODY chunk, so end playback.
G_CheckDemoStatus ();
break;
}
id = ReadInt8 (&demo_p);
id = ReadInt8 (demo_p);
switch (id)
{
@ -2514,7 +2514,7 @@ void G_ReadDemoTiccmd (usercmd_t *cmd, int player)
case DEM_DROPPLAYER:
{
uint8_t i = ReadInt8 (&demo_p);
uint8_t i = ReadInt8 (demo_p);
if (i < MAXPLAYERS)
{
playeringame[i] = false;
@ -2523,7 +2523,7 @@ void G_ReadDemoTiccmd (usercmd_t *cmd, int player)
break;
default:
Net_DoCommand (id, &demo_p, player);
Net_DoCommand (id, demo_p, player);
break;
}
}
@ -2556,8 +2556,7 @@ void G_WriteDemoTiccmd (usercmd_t *cmd, int player, int buf)
// [RH] Write any special "ticcmds" for this player to the demo
if ((specdata = ClientStates[player].Tics[buf % BACKUPTICS].Data.GetData (&speclen)) && !(gametic % TicDup))
{
memcpy (demo_p, specdata, speclen);
demo_p += speclen;
WriteBytes(TArrayView(specdata, speclen), demo_p);
ClientStates[player].Tics[buf % BACKUPTICS].Data.SetData(nullptr, 0);
}
@ -2566,19 +2565,19 @@ void G_WriteDemoTiccmd (usercmd_t *cmd, int player, int buf)
WriteUserCmdMessage (*cmd, &players[player].cmd, demo_p);
// [RH] Bigger safety margin
if (demo_p > demobuffer + maxdemosize - 64)
if (demo_p.Data() > demobuffer.Data() + demobuffer.Size() - 64)
{
ptrdiff_t pos = demo_p - demobuffer;
ptrdiff_t spot = streamPos - demobuffer;
ptrdiff_t comp = democompspot - demobuffer;
ptrdiff_t body = demobodyspot - demobuffer;
ptrdiff_t pos = demo_p.Data() - demobuffer.Data();
ptrdiff_t spot = streamPos - demobuffer.Data();
ptrdiff_t comp = democompspot - demobuffer.Data();
ptrdiff_t body = demobodyspot - demobuffer.Data();
// [RH] Allocate more space for the demo
maxdemosize += 0x20000;
demobuffer = (uint8_t *)M_Realloc (demobuffer, maxdemosize);
demo_p = demobuffer + pos;
streamPos = demobuffer + spot;
democompspot = demobuffer + comp;
demobodyspot = demobuffer + body;
demobuffer.Resize(maxdemosize);
demo_p = TArrayView(demobuffer.Data() + pos, demobuffer.Size() - pos);
streamPos = demobuffer.Data() + spot;
democompspot = demobuffer.Data() + comp;
demobodyspot = demobuffer.Data() + body;
}
}
@ -2594,7 +2593,7 @@ void G_RecordDemo (const char* name)
FixPathSeperator (demoname);
DefaultExtension (demoname, ".lmp");
maxdemosize = 0x20000;
demobuffer = (uint8_t *)M_Malloc (maxdemosize);
demobuffer.Resize(maxdemosize);
demorecording = true;
}
@ -2611,35 +2610,33 @@ void G_BeginRecording (const char *startmap)
{
startmap = primaryLevel->MapName.GetChars();
}
demo_p = demobuffer;
demo_p = TArrayView(demobuffer.Data(), demobuffer.Size());
WriteInt32 (FORM_ID, &demo_p); // Write FORM ID
demo_p += 4; // Leave space for len
WriteInt32 (ZDEM_ID, &demo_p); // Write ZDEM ID
WriteInt32 (FORM_ID, demo_p); // Write FORM ID
AdvanceStream(demo_p, 4); // Leave space for len
WriteInt32 (ZDEM_ID, demo_p); // Write ZDEM ID
// Write header chunk
StartChunk (ZDHD_ID, &demo_p);
WriteInt16 (DEMOGAMEVERSION, &demo_p); // Write ZDoom version
*demo_p++ = 2; // Write minimum version needed to use this demo.
*demo_p++ = 3; // (Useful?)
StartChunk (ZDHD_ID, demo_p);
WriteInt16 (DEMOGAMEVERSION, demo_p); // Write ZDoom version
WriteInt8(2, demo_p); // Write minimum version needed to use this demo.
WriteInt8(3, demo_p); // (Useful?)
strcpy((char*)demo_p, startmap); // Write name of map demo was recorded on.
demo_p += strlen(startmap) + 1;
WriteInt32(rngseed, &demo_p); // Write RNG seed
*demo_p++ = consoleplayer;
FinishChunk (&demo_p);
WriteString(startmap, demo_p); // Write name of map demo was recorded on.
WriteInt32(rngseed, demo_p); // Write RNG seed
WriteInt8(consoleplayer, demo_p);
FinishChunk (demo_p);
// Write player info chunks
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i])
{
StartChunk(UINF_ID, &demo_p);
WriteInt8((uint8_t)i, &demo_p);
StartChunk(UINF_ID, demo_p);
WriteInt8((uint8_t)i, demo_p);
auto str = D_GetUserInfoStrings(i);
memcpy(demo_p, str.GetChars(), str.Len() + 1);
demo_p += str.Len() + 1;
FinishChunk(&demo_p);
WriteFString(str, demo_p);
FinishChunk(demo_p);
}
}
@ -2648,29 +2645,29 @@ void G_BeginRecording (const char *startmap)
// enough.
if (multiplayer)
{
StartChunk (NETD_ID, &demo_p);
FinishChunk (&demo_p);
StartChunk (NETD_ID, demo_p);
FinishChunk (demo_p);
}
// Write cvars chunk
StartChunk (VARS_ID, &demo_p);
C_WriteCVars (&demo_p, CVAR_SERVERINFO|CVAR_DEMOSAVE);
FinishChunk (&demo_p);
StartChunk (VARS_ID, demo_p);
C_WriteCVars (demo_p, CVAR_SERVERINFO|CVAR_DEMOSAVE);
FinishChunk (demo_p);
// Write weapon ordering chunk
StartChunk (WEAP_ID, &demo_p);
P_WriteDemoWeaponsChunk(&demo_p);
FinishChunk (&demo_p);
StartChunk (WEAP_ID, demo_p);
P_WriteDemoWeaponsChunk(demo_p);
FinishChunk (demo_p);
// Indicate body is compressed
StartChunk (COMP_ID, &demo_p);
democompspot = demo_p;
WriteInt32 (0, &demo_p);
FinishChunk (&demo_p);
StartChunk (COMP_ID, demo_p);
democompspot = demo_p.Data();
WriteInt32 (0, demo_p);
FinishChunk (demo_p);
// Begin BODY chunk
StartChunk (BODY_ID, &demo_p);
demobodyspot = demo_p;
StartChunk (BODY_ID, demo_p);
demobodyspot = demo_p.Data();
}
@ -2730,13 +2727,13 @@ bool G_ProcessIFFDemo (FString &mapname)
for (i = 0; i < MAXPLAYERS; i++)
playeringame[i] = 0;
len = ReadInt32 (&demo_p);
zdemformend = demo_p + len + (len & 1);
len = ReadInt32 (demo_p);
zdemformend = demo_p.Data() + len + (len & 1);
// Check to make sure this is a ZDEM chunk file.
// TODO: Support multiple FORM ZDEMs in a CAT. Might be useful.
id = ReadInt32 (&demo_p);
id = ReadInt32 (demo_p);
if (id != ZDEM_ID)
{
Printf ("Not a " GAMENAME " demo file!\n");
@ -2745,11 +2742,11 @@ bool G_ProcessIFFDemo (FString &mapname)
// Process all chunks until a BODY chunk is encountered.
while (demo_p < zdemformend && !bodyHit)
while (demo_p.Data() < zdemformend && !bodyHit)
{
id = ReadInt32 (&demo_p);
len = ReadInt32 (&demo_p);
nextchunk = demo_p + len + (len & 1);
id = ReadInt32 (demo_p);
len = ReadInt32 (demo_p);
nextchunk = demo_p.Data() + len + (len & 1);
if (nextchunk > zdemformend)
{
Printf ("Demo is mangled!\n");
@ -2761,48 +2758,47 @@ bool G_ProcessIFFDemo (FString &mapname)
case ZDHD_ID:
headerHit = true;
demover = ReadInt16 (&demo_p); // ZDoom version demo was created with
demover = ReadInt16 (demo_p); // ZDoom version demo was created with
if (demover < MINDEMOVERSION)
{
Printf ("Demo requires an older version of " GAMENAME "!\n");
//return true;
}
if (ReadInt16 (&demo_p) > DEMOGAMEVERSION) // Minimum ZDoom version
if (ReadInt16 (demo_p) > DEMOGAMEVERSION) // Minimum ZDoom version
{
Printf ("Demo requires a newer version of " GAMENAME "!\n");
return true;
}
if (demover >= 0x21a)
{
mapname = (char*)demo_p;
demo_p += mapname.Len() + 1;
mapname = ReadStringConst(demo_p);
}
else
{
mapname = FString((char*)demo_p, 8);
demo_p += 8;
mapname = FString((char*)demo_p.Data(), 8);
AdvanceStream(demo_p, 8);
}
rngseed = ReadInt32 (&demo_p);
rngseed = ReadInt32 (demo_p);
// Only reset the RNG if this demo is not in conjunction with a savegame.
if (mapname[0] != 0)
{
FRandom::StaticClearRandom ();
}
consoleplayer = *demo_p++;
consoleplayer = ReadInt8(demo_p);
break;
case VARS_ID:
C_ReadCVars (&demo_p);
C_ReadCVars (demo_p);
break;
case UINF_ID:
i = ReadInt8 (&demo_p);
i = ReadInt8 (demo_p);
if (!playeringame[i])
{
playeringame[i] = 1;
numPlayers++;
}
D_ReadUserInfoStrings (i, &demo_p, false);
D_ReadUserInfoStrings (i, demo_p, false);
break;
case NETD_ID:
@ -2810,21 +2806,21 @@ bool G_ProcessIFFDemo (FString &mapname)
break;
case WEAP_ID:
P_ReadDemoWeaponsChunk(&demo_p);
P_ReadDemoWeaponsChunk(demo_p);
break;
case BODY_ID:
bodyHit = true;
zdembodyend = demo_p + len;
zdembodyend = demo_p.Data() + len;
break;
case COMP_ID:
uncompSize = ReadInt32 (&demo_p);
uncompSize = ReadInt32 (demo_p);
break;
}
if (!bodyHit)
demo_p = nextchunk;
demo_p = TArrayView(nextchunk, demo_p.Size() + demo_p.Data() - nextchunk);
}
if (!headerHit)
@ -2851,17 +2847,16 @@ bool G_ProcessIFFDemo (FString &mapname)
if (uncompSize > 0)
{
uint8_t *uncompressed = (uint8_t*)M_Malloc(uncompSize);
int r = uncompress (uncompressed, &uncompSize, demo_p, uLong(zdembodyend - demo_p));
auto uncompressed = TArray<uint8_t>(uncompSize, true);
int r = uncompress (uncompressed.Data(), &uncompSize, demo_p.Data(), uLong(zdembodyend - demo_p.Data()));
if (r != Z_OK)
{
Printf ("Could not decompress demo! %s\n", M_ZLibError(r).GetChars());
M_Free(uncompressed);
return true;
}
M_Free (demobuffer);
zdembodyend = uncompressed + uncompSize;
demobuffer = demo_p = uncompressed;
zdembodyend = uncompressed.Data() + uncompSize;
demobuffer = std::move(uncompressed);
demo_p = TArrayView(demobuffer.Data(), uncompSize);
}
return false;
@ -2878,9 +2873,9 @@ void G_DoPlayDemo (void)
demolump = fileSystem.CheckNumForFullName (defdemoname.GetChars(), true);
if (demolump >= 0)
{
int demolen = fileSystem.FileLength (demolump);
demobuffer = (uint8_t *)M_Malloc(demolen);
fileSystem.ReadFile (demolump, demobuffer);
size_t demolen = fileSystem.FileLength (demolump);
demobuffer.Resize(demolen);
fileSystem.ReadFile (demolump, demobuffer.Data());
}
else
{
@ -2891,26 +2886,26 @@ void G_DoPlayDemo (void)
{
I_Error("Unable to open demo '%s'", defdemoname.GetChars());
}
auto len = fr.GetLength();
demobuffer = (uint8_t*)M_Malloc(len);
if (fr.Read(demobuffer, len) != len)
size_t demolen = fr.GetLength();
demobuffer.Resize(demolen);
if (fr.Read(demobuffer.Data(), demolen) != demolen)
{
I_Error("Unable to read demo '%s'", defdemoname.GetChars());
}
}
demo_p = demobuffer;
demo_p = TArrayView(demobuffer.Data(), demobuffer.Size());
if (singledemo) Printf ("Playing demo %s\n", defdemoname.GetChars());
C_BackupCVars (); // [RH] Save cvars that might be affected by demo
if (ReadInt32 (&demo_p) != FORM_ID)
if (ReadInt32 (demo_p) != FORM_ID)
{
const char *eek = "Cannot play non-" GAMENAME " demos.\n";
C_ForgetCVars();
M_Free(demobuffer);
demo_p = demobuffer = NULL;
demobuffer.Reset();
demo_p = NULL;
if (singledemo)
{
I_Error ("%s", eek);
@ -2990,8 +2985,7 @@ bool G_CheckDemoStatus (void)
endtime = I_GetTime () - starttime;
C_RestoreCVars (); // [RH] Restore cvars demo might have changed
M_Free (demobuffer);
demobuffer = NULL;
demobuffer.Reset();
P_SetupWeapons_ntohton();
demoplayback = false;
@ -3034,9 +3028,7 @@ bool G_CheckDemoStatus (void)
if (demorecording)
{
uint8_t *formlen;
WriteInt8 (DEM_STOP, &demo_p);
WriteInt8 (DEM_STOP, demo_p);
if (demo_compress)
{
@ -3044,32 +3036,31 @@ bool G_CheckDemoStatus (void)
// a compressed version. If the BODY successfully compresses, the
// contents of the COMP chunk will be changed to indicate the
// uncompressed size of the BODY.
uLong len = uLong(demo_p - demobodyspot);
uLong len = uLong(demo_p.Data() - demobodyspot);
uLong outlen = (len + len/100 + 12);
TArray<Byte> compressed(outlen, true);
int r = compress2 (compressed.Data(), &outlen, demobodyspot, len, 9);
if (r == Z_OK && outlen < len)
{
formlen = democompspot;
WriteInt32 (len, &democompspot);
UncheckedWriteInt32 (len, &democompspot);
memcpy (demobodyspot, compressed.Data(), outlen);
demo_p = demobodyspot + outlen;
demo_p = TArrayView(demobodyspot + outlen, demo_p.Size() + len - outlen);
}
}
FinishChunk (&demo_p);
formlen = demobuffer + 4;
WriteInt32 (int(demo_p - demobuffer - 8), &formlen);
FinishChunk (demo_p);
uint8_t* formlen = demobuffer.Data() + 4;
UncheckedWriteInt32 (int(demo_p.Data() - demobuffer.Data() - 8), &formlen);
auto fw = FileWriter::Open(demoname.GetChars());
bool saved = false;
if (fw != nullptr)
{
const size_t size = demo_p - demobuffer;
saved = fw->Write(demobuffer, size) == size;
const size_t size = demo_p.Data() - demobuffer.Data();
saved = fw->Write(demobuffer.Data(), size) == size;
delete fw;
if (!saved) RemoveFile(demoname.GetChars());
}
M_Free (demobuffer);
demobuffer.Reset();
demorecording = false;
stoprecording = false;
if (saved)