Skip to content

Commit

Permalink
Validate ticks when reading demo chunk headers
Browse files Browse the repository at this point in the history
Add checks to ensure that the ticks read from demo chunk headers are in the valid range (cf. `MIN_TICK` and `MAX_TICK` constants). Playing demos with invalid ticks is prevented entirely, as the invalid ticks would cause issues in other places. The server never uses ticks outside the valid range, so invalid ticks should never occur in correctly recorded demos.

Additionally, checks are added to detect if a tickmarker chunk with a tick delta occurs before a tickmarker chunk defining an initial tick. The tick delta is only meaningful when an initial tick has already been defined, so if this is not the case the demo is also considered invalid.
  • Loading branch information
Robyt3 committed Oct 19, 2023
1 parent 6951795 commit 92e2e17
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 17 deletions.
63 changes: 48 additions & 15 deletions src/engine/shared/demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -418,37 +418,45 @@ void CDemoPlayer::SetListener(IListener *pListener)
m_pListener = pListener;
}

int CDemoPlayer::ReadChunkHeader(int *pType, int *pSize, int *pTick)
CDemoPlayer::EReadChunkHeaderResult CDemoPlayer::ReadChunkHeader(int *pType, int *pSize, int *pTick)
{
*pSize = 0;
*pType = 0;

unsigned char Chunk = 0;
if(io_read(m_File, &Chunk, sizeof(Chunk)) != sizeof(Chunk))
return -1;
return CHUNKHEADER_EOF;

if(Chunk & CHUNKTYPEFLAG_TICKMARKER)
{
// decode tick marker
int Tickdelta_legacy = Chunk & CHUNKMASK_TICK_LEGACY; // compatibility
*pType = Chunk & (CHUNKTYPEFLAG_TICKMARKER | CHUNKTICKFLAG_KEYFRAME);

int NewTick;
if(m_Info.m_Header.m_Version < gs_VersionTickCompression && Tickdelta_legacy != 0)
{
*pTick += Tickdelta_legacy;
if(*pTick < 0) // initial tick not initialized before a tick delta
return CHUNKHEADER_ERROR;
NewTick = *pTick + Tickdelta_legacy;
}
else if(Chunk & CHUNKTICKFLAG_TICK_COMPRESSED)
{
if(*pTick < 0) // initial tick not initialized before a tick delta
return CHUNKHEADER_ERROR;
int Tickdelta = Chunk & CHUNKMASK_TICK;
*pTick += Tickdelta;
NewTick = *pTick + Tickdelta;
}
else
{
unsigned char aTickdata[sizeof(int32_t)];
if(io_read(m_File, aTickdata, sizeof(aTickdata)) != sizeof(aTickdata))
return -1;
*pTick = bytes_be_to_uint(aTickdata);
return CHUNKHEADER_ERROR;
NewTick = bytes_be_to_uint(aTickdata);
}
if(NewTick < MIN_TICK || NewTick >= MAX_TICK) // invalid tick
return CHUNKHEADER_ERROR;
*pTick = NewTick;
}
else
{
Expand All @@ -460,34 +468,42 @@ int CDemoPlayer::ReadChunkHeader(int *pType, int *pSize, int *pTick)
{
unsigned char aSizedata[1];
if(io_read(m_File, aSizedata, sizeof(aSizedata)) != sizeof(aSizedata))
return -1;
return CHUNKHEADER_ERROR;
*pSize = aSizedata[0];
}
else if(*pSize == 31)
{
unsigned char aSizedata[2];
if(io_read(m_File, aSizedata, sizeof(aSizedata)) != sizeof(aSizedata))
return -1;
return CHUNKHEADER_ERROR;
*pSize = (aSizedata[1] << 8) | aSizedata[0];
}
}

return 0;
return CHUNKHEADER_SUCCESS;
}

void CDemoPlayer::ScanFile()
bool CDemoPlayer::ScanFile()
{
const long StartPos = io_tell(m_File);
m_vKeyFrames.clear();

int ChunkTick = 0;
int ChunkTick = -1;
while(true)
{
const long CurrentPos = io_tell(m_File);

int ChunkType, ChunkSize;
if(ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick))
const EReadChunkHeaderResult Result = ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick);
if(Result == CHUNKHEADER_EOF)
{
break;
}
else if(Result == CHUNKHEADER_ERROR)
{
m_vKeyFrames.clear();
return false;
}

if(ChunkType & CHUNKTYPEFLAG_TICKMARKER)
{
Expand All @@ -505,6 +521,7 @@ void CDemoPlayer::ScanFile()
}

io_seek(m_File, StartPos, IOSEEK_START);
return true;
}

void CDemoPlayer::DoTick()
Expand All @@ -527,11 +544,18 @@ void CDemoPlayer::DoTick()
while(true)
{
int ChunkType, ChunkSize;
if(ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick))
const EReadChunkHeaderResult Result = ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick);
if(Result != CHUNKHEADER_SUCCESS)
{
// stop on error or eof
if(m_pConsole)
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", "end of file");
{
if(Result == CHUNKHEADER_EOF)
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", "end of file");
else
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_player", "error reading chunk header");
}

#if defined(CONF_VIDEORECORDER)
if(m_UseVideo && IVideo::Current())
Stop();
Expand Down Expand Up @@ -782,7 +806,16 @@ int CDemoPlayer::Load(class IStorage *pStorage, class IConsole *pConsole, const
}

// scan the file for interesting points
ScanFile();
if(!ScanFile())
{
if(m_pConsole)
{
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_player", "Error scanning demo file");
}
io_close(m_File);
m_File = 0;
return -1;
}

// reset slice markers
g_Config.m_ClDemoSliceBegin = -1;
Expand Down
10 changes: 8 additions & 2 deletions src/engine/shared/demo.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,15 @@ class CDemoPlayer : public IDemoPlayer
bool m_UseVideo;
bool m_WasRecording = false;

int ReadChunkHeader(int *pType, int *pSize, int *pTick);
enum EReadChunkHeaderResult
{
CHUNKHEADER_SUCCESS,
CHUNKHEADER_ERROR,
CHUNKHEADER_EOF,
};
EReadChunkHeaderResult ReadChunkHeader(int *pType, int *pSize, int *pTick);
void DoTick();
void ScanFile();
bool ScanFile();

int64_t Time();

Expand Down

0 comments on commit 92e2e17

Please sign in to comment.