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

@ -38,7 +38,10 @@
#include "cmdlib.h"
#include "serializer.h"
char* ReadString(uint8_t **stream)
// Unchecked stream functions.
// Use the checked versions instead unless you're checking the stream size yourself!
char* UncheckedReadString(uint8_t **stream)
{
char *string = *((char **)stream);
@ -46,65 +49,65 @@ char* ReadString(uint8_t **stream)
return copystring(string);
}
const char* ReadStringConst(uint8_t **stream)
const char* UncheckedReadStringConst(uint8_t **stream)
{
const char *string = *((const char **)stream);
*stream += strlen(string) + 1;
return string;
}
uint8_t ReadInt8(uint8_t **stream)
uint8_t UncheckedReadInt8(uint8_t **stream)
{
uint8_t v = **stream;
*stream += 1;
return v;
}
int16_t ReadInt16(uint8_t **stream)
int16_t UncheckedReadInt16(uint8_t **stream)
{
int16_t v = (((*stream)[0]) << 8) | (((*stream)[1]));
*stream += 2;
return v;
}
int32_t ReadInt32(uint8_t **stream)
int32_t UncheckedReadInt32(uint8_t **stream)
{
int32_t v = (((*stream)[0]) << 24) | (((*stream)[1]) << 16) | (((*stream)[2]) << 8) | (((*stream)[3]));
*stream += 4;
return v;
}
int64_t ReadInt64(uint8_t** stream)
int64_t UncheckedReadInt64(uint8_t **stream)
{
int64_t v = (int64_t((*stream)[0]) << 56) | (int64_t((*stream)[1]) << 48) | (int64_t((*stream)[2]) << 40) | (int64_t((*stream)[3]) << 32)
| (int64_t((*stream)[4]) << 24) | (int64_t((*stream)[5]) << 16) | (int64_t((*stream)[6]) << 8) | (int64_t((*stream)[7]));
| (int64_t((*stream)[4]) << 24) | (int64_t((*stream)[5]) << 16) | (int64_t((*stream)[6]) << 8) | (int64_t((*stream)[7]));
*stream += 8;
return v;
}
float ReadFloat(uint8_t **stream)
float UncheckedReadFloat(uint8_t **stream)
{
union
{
int32_t i;
float f;
} fakeint;
fakeint.i = ReadInt32 (stream);
fakeint.i = UncheckedReadInt32(stream);
return fakeint.f;
}
double ReadDouble(uint8_t** stream)
double UncheckedReadDouble(uint8_t **stream)
{
union
{
int64_t i;
double f;
} fakeint;
fakeint.i = ReadInt64(stream);
fakeint.i = UncheckedReadInt64(stream);
return fakeint.f;
}
void WriteString(const char *string, uint8_t **stream)
void UncheckedWriteString(const char *string, uint8_t **stream)
{
char *p = *((char **)stream);
@ -116,20 +119,20 @@ void WriteString(const char *string, uint8_t **stream)
*stream = (uint8_t *)p;
}
void WriteInt8(uint8_t v, uint8_t **stream)
void UncheckedWriteInt8(uint8_t v, uint8_t **stream)
{
**stream = v;
*stream += 1;
}
void WriteInt16(int16_t v, uint8_t **stream)
void UncheckedWriteInt16(int16_t v, uint8_t **stream)
{
(*stream)[0] = v >> 8;
(*stream)[1] = v & 255;
*stream += 2;
}
void WriteInt32(int32_t v, uint8_t **stream)
void UncheckedWriteInt32(int32_t v, uint8_t **stream)
{
(*stream)[0] = v >> 24;
(*stream)[1] = (v >> 16) & 255;
@ -138,7 +141,7 @@ void WriteInt32(int32_t v, uint8_t **stream)
*stream += 4;
}
void WriteInt64(int64_t v, uint8_t** stream)
void UncheckedWriteInt64(int64_t v, uint8_t **stream)
{
(*stream)[0] = v >> 56;
(*stream)[1] = (v >> 48) & 255;
@ -151,7 +154,231 @@ void WriteInt64(int64_t v, uint8_t** stream)
*stream += 8;
}
void WriteFloat(float v, uint8_t **stream)
void UncheckedWriteFloat(float v, uint8_t **stream)
{
union
{
int32_t i;
float f;
} fakeint;
fakeint.f = v;
UncheckedWriteInt32(fakeint.i, stream);
}
void UncheckedWriteDouble(double v, uint8_t **stream)
{
union
{
int64_t i;
double f;
} fakeint;
fakeint.f = v;
UncheckedWriteInt64(fakeint.i, stream);
}
void AdvanceStream(TArrayView<uint8_t>& stream, size_t bytes)
{
assert(bytes <= stream.Size());
stream = TArrayView(stream.Data() + bytes, stream.Size() - bytes);
}
// Checked stream functions
char* ReadString(TArrayView<uint8_t>& stream)
{
char *string = (char*)stream.Data();
size_t len = strnlen(string, stream.Size());
if (len == stream.Size())
{
I_Error("Attempted to read past end of stream");
}
AdvanceStream(stream, len + 1);
return copystring(string);
}
const char* ReadStringConst(TArrayView<uint8_t>& stream)
{
const char* string = (const char*)stream.Data();
size_t len = strnlen(string, stream.Size());
if (len == stream.Size())
{
I_Error("Attempted to read past end of stream");
}
AdvanceStream(stream, len + 1);
return string;
}
void ReadBytes(TArrayView<uint8_t>& dst, TArrayView<uint8_t>& stream)
{
if (dst.Size() > stream.Size())
{
I_Error("Attempted to read past end of stream");
}
if (dst.Size())
{
memcpy(dst.Data(), stream.Data(), dst.Size());
AdvanceStream(stream, dst.Size());
}
}
uint8_t ReadInt8(TArrayView<uint8_t>& stream)
{
if (stream.Size() < 1)
{
I_Error("Attempted to read past end of stream");
}
uint8_t v = stream[0];
AdvanceStream(stream, 1);
return v;
}
int16_t ReadInt16(TArrayView<uint8_t>& stream)
{
if (stream.Size() < 2)
{
I_Error("Attempted to read past end of stream");
}
int16_t v = ((stream[0]) << 8) | stream[1];
AdvanceStream(stream, 2);
return v;
}
int32_t ReadInt32(TArrayView<uint8_t>& stream)
{
if (stream.Size() < 4)
{
I_Error("Attempted to read past end of stream");
}
int32_t v = (stream[0] << 24) | (stream[1] << 16) | (stream[2] << 8) | stream[3];
AdvanceStream(stream, 4);
return v;
}
int64_t ReadInt64(TArrayView<uint8_t>& stream)
{
if (stream.Size() < 8)
{
I_Error("Attempted to read past end of stream");
}
int64_t v = (int64_t(stream[0]) << 56) | (int64_t(stream[1]) << 48) | (int64_t(stream[2]) << 40) | (int64_t(stream[3]) << 32)
| (int64_t(stream[4]) << 24) | (int64_t(stream[5]) << 16) | (int64_t(stream[6]) << 8) | int64_t(stream[7]);
AdvanceStream(stream, 8);
return v;
}
float ReadFloat(TArrayView<uint8_t>& stream)
{
union
{
int32_t i;
float f;
} fakeint;
fakeint.i = ReadInt32 (stream);
return fakeint.f;
}
double ReadDouble(TArrayView<uint8_t>& stream)
{
union
{
int64_t i;
double f;
} fakeint;
fakeint.i = ReadInt64(stream);
return fakeint.f;
}
void WriteString(const char *string, TArrayView<uint8_t>& stream)
{
char *p = (char *)stream.Data();
unsigned int remaining = stream.Size();
while (*string) {
if (remaining-- == 0)
{
I_Error("Attempted to write past end of stream");
}
*p++ = *string++;
}
if (remaining == 0)
{
I_Error("Attempted to write past end of stream");
}
*p++ = 0;
AdvanceStream(stream, p - (char*)stream.Data());
}
void WriteBytes(const TArrayView<uint8_t>& source, TArrayView<uint8_t>& stream)
{
if (source.Size() > stream.Size())
{
I_Error("Attempted to write past end of stream");
}
if (source.Size())
{
memcpy(stream.Data(), source.Data(), source.Size());
AdvanceStream(stream, source.Size());
}
}
void WriteFString(FString& string, TArrayView<uint8_t>& stream)
{
WriteBytes(string.GetTArrayView(), stream);
}
void WriteInt8(uint8_t v, TArrayView<uint8_t>& stream)
{
if (stream.Size() < 1)
{
I_Error("Attempted to write past end of stream");
}
stream[0] = v;
AdvanceStream(stream, 1);
}
void WriteInt16(int16_t v, TArrayView<uint8_t>& stream)
{
if (stream.Size() < 2)
{
I_Error("Attempted to write past end of stream");
}
stream[0] = v >> 8;
stream[1] = v & 255;
AdvanceStream(stream, 2);
}
void WriteInt32(int32_t v, TArrayView<uint8_t>& stream)
{
if (stream.Size() < 4)
{
I_Error("Attempted to write past end of stream");
}
stream[0] = v >> 24;
stream[1] = (v >> 16) & 255;
stream[2] = (v >> 8) & 255;
stream[3] = v & 255;
AdvanceStream(stream, 4);
}
void WriteInt64(int64_t v, TArrayView<uint8_t>& stream)
{
if (stream.Size() < 8)
{
I_Error("Attempted to write past end of stream");
}
stream[0] = v >> 56;
stream[1] = (v >> 48) & 255;
stream[2] = (v >> 40) & 255;
stream[3] = (v >> 32) & 255;
stream[4] = (v >> 24) & 255;
stream[5] = (v >> 16) & 255;
stream[6] = (v >> 8) & 255;
stream[7] = v & 255;
AdvanceStream(stream, 8);
}
void WriteFloat(float v, TArrayView<uint8_t>& stream)
{
union
{
@ -162,7 +389,7 @@ void WriteFloat(float v, uint8_t **stream)
WriteInt32 (fakeint.i, stream);
}
void WriteDouble(double v, uint8_t** stream)
void WriteDouble(double v, TArrayView<uint8_t>& stream)
{
union
{
@ -193,11 +420,8 @@ FSerializer& Serialize(FSerializer& arc, const char* key, usercmd_t& cmd, usercm
return arc;
}
// Returns the number of bytes read
int UnpackUserCmd(usercmd_t& cmd, const usercmd_t* basis, uint8_t*& stream)
void UnpackUserCmd(usercmd_t& cmd, const usercmd_t* basis, TArrayView<uint8_t>& stream)
{
const uint8_t* start = stream;
if (basis != nullptr)
{
if (basis != &cmd)
@ -208,7 +432,7 @@ int UnpackUserCmd(usercmd_t& cmd, const usercmd_t* basis, uint8_t*& stream)
memset(&cmd, 0, sizeof(usercmd_t));
}
uint8_t flags = ReadInt8(&stream);
uint8_t flags = ReadInt8(stream);
if (flags)
{
// We can support up to 29 buttons using 1 to 4 bytes to store them. The most
@ -216,19 +440,19 @@ int UnpackUserCmd(usercmd_t& cmd, const usercmd_t* basis, uint8_t*& stream)
// should be read in excluding the last one which supports all 8 bits.
if (flags & UCMDF_BUTTONS)
{
uint8_t in = ReadInt8(&stream);
uint8_t in = ReadInt8(stream);
uint32_t buttons = (cmd.buttons & ~0x7F) | (in & 0x7F);
if (in & MoreButtons)
{
in = ReadInt8(&stream);
in = ReadInt8(stream);
buttons = (buttons & ~(0x7F << 7)) | ((in & 0x7F) << 7);
if (in & MoreButtons)
{
in = ReadInt8(&stream);
in = ReadInt8(stream);
buttons = (buttons & ~(0x7F << 14)) | ((in & 0x7F) << 14);
if (in & MoreButtons)
{
in = ReadInt8(&stream);
in = ReadInt8(stream);
buttons = (buttons & ~(0xFF << 21)) | (in << 21);
}
}
@ -236,26 +460,24 @@ int UnpackUserCmd(usercmd_t& cmd, const usercmd_t* basis, uint8_t*& stream)
cmd.buttons = buttons;
}
if (flags & UCMDF_PITCH)
cmd.pitch = ReadInt16(&stream);
cmd.pitch = ReadInt16(stream);
if (flags & UCMDF_YAW)
cmd.yaw = ReadInt16(&stream);
cmd.yaw = ReadInt16(stream);
if (flags & UCMDF_FORWARDMOVE)
cmd.forwardmove = ReadInt16(&stream);
cmd.forwardmove = ReadInt16(stream);
if (flags & UCMDF_SIDEMOVE)
cmd.sidemove = ReadInt16(&stream);
cmd.sidemove = ReadInt16(stream);
if (flags & UCMDF_UPMOVE)
cmd.upmove = ReadInt16(&stream);
cmd.upmove = ReadInt16(stream);
if (flags & UCMDF_ROLL)
cmd.roll = ReadInt16(&stream);
cmd.roll = ReadInt16(stream);
}
return int(stream - start);
}
int PackUserCmd(const usercmd_t& cmd, const usercmd_t* basis, uint8_t*& stream)
void PackUserCmd(const usercmd_t& cmd, const usercmd_t* basis, TArrayView<uint8_t>& stream)
{
uint8_t flags = 0;
uint8_t* start = stream;
auto flagsPosition = TArrayView(stream.Data(), 1);
usercmd_t blank;
if (basis == nullptr)
@ -264,7 +486,7 @@ int PackUserCmd(const usercmd_t& cmd, const usercmd_t* basis, uint8_t*& stream)
basis = &blank;
}
++stream; // Make room for the flags.
AdvanceStream(stream, 1); // Make room for the flags.
uint32_t buttons_changed = cmd.buttons ^ basis->buttons;
if (buttons_changed != 0)
{
@ -284,56 +506,54 @@ int PackUserCmd(const usercmd_t& cmd, const usercmd_t* basis, uint8_t*& stream)
bytes[2] |= MoreButtons;
}
}
WriteInt8(bytes[0], &stream);
WriteInt8(bytes[0], stream);
if (bytes[0] & MoreButtons)
{
WriteInt8(bytes[1], &stream);
WriteInt8(bytes[1], stream);
if (bytes[1] & MoreButtons)
{
WriteInt8(bytes[2], &stream);
WriteInt8(bytes[2], stream);
if (bytes[2] & MoreButtons)
WriteInt8(bytes[3], &stream);
WriteInt8(bytes[3], stream);
}
}
}
if (cmd.pitch != basis->pitch)
{
flags |= UCMDF_PITCH;
WriteInt16(cmd.pitch, &stream);
WriteInt16(cmd.pitch, stream);
}
if (cmd.yaw != basis->yaw)
{
flags |= UCMDF_YAW;
WriteInt16 (cmd.yaw, &stream);
WriteInt16 (cmd.yaw, stream);
}
if (cmd.forwardmove != basis->forwardmove)
{
flags |= UCMDF_FORWARDMOVE;
WriteInt16 (cmd.forwardmove, &stream);
WriteInt16 (cmd.forwardmove, stream);
}
if (cmd.sidemove != basis->sidemove)
{
flags |= UCMDF_SIDEMOVE;
WriteInt16(cmd.sidemove, &stream);
WriteInt16(cmd.sidemove, stream);
}
if (cmd.upmove != basis->upmove)
{
flags |= UCMDF_UPMOVE;
WriteInt16(cmd.upmove, &stream);
WriteInt16(cmd.upmove, stream);
}
if (cmd.roll != basis->roll)
{
flags |= UCMDF_ROLL;
WriteInt16(cmd.roll, &stream);
WriteInt16(cmd.roll, stream);
}
// Write the packing bits
WriteInt8(flags, &start);
return int(stream - start);
WriteInt8(flags, flagsPosition);
}
int WriteUserCmdMessage(const usercmd_t& cmd, const usercmd_t* basis, uint8_t*& stream)
void WriteUserCmdMessage(const usercmd_t& cmd, const usercmd_t* basis, TArrayView<uint8_t>& stream)
{
if (basis == nullptr)
{
@ -341,58 +561,60 @@ int WriteUserCmdMessage(const usercmd_t& cmd, const usercmd_t* basis, uint8_t*&
|| cmd.pitch || cmd.yaw || cmd.roll
|| cmd.forwardmove || cmd.sidemove || cmd.upmove)
{
WriteInt8(DEM_USERCMD, &stream);
return PackUserCmd(cmd, basis, stream) + 1;
WriteInt8(DEM_USERCMD, stream);
PackUserCmd(cmd, basis, stream);
return;
}
}
else if (cmd.buttons != basis->buttons
|| cmd.yaw != basis->yaw || cmd.pitch != basis->pitch || cmd.roll != basis->roll
|| cmd.forwardmove != basis->forwardmove || cmd.sidemove != basis->sidemove || cmd.upmove != basis->upmove)
{
WriteInt8(DEM_USERCMD, &stream);
return PackUserCmd(cmd, basis, stream) + 1;
WriteInt8(DEM_USERCMD, stream);
PackUserCmd(cmd, basis, stream);
return;
}
WriteInt8(DEM_EMPTYUSERCMD, &stream);
return 1;
WriteInt8(DEM_EMPTYUSERCMD, stream);
}
// Reads through the user command without actually setting any of its info. Used to get the size
// of the command when getting the length of the stream.
int SkipUserCmdMessage(uint8_t*& stream)
void SkipUserCmdMessage(TArrayView<uint8_t>& stream)
{
const uint8_t* start = stream;
while (true)
{
const uint8_t type = *stream++;
const uint8_t type = ReadInt8(stream);
if (type == DEM_USERCMD)
{
int skip = 1;
if (*stream & UCMDF_PITCH)
if (stream[0] & UCMDF_PITCH)
skip += 2;
if (*stream & UCMDF_YAW)
if (stream[0] & UCMDF_YAW)
skip += 2;
if (*stream & UCMDF_FORWARDMOVE)
if (stream[0] & UCMDF_FORWARDMOVE)
skip += 2;
if (*stream & UCMDF_SIDEMOVE)
if (stream[0] & UCMDF_SIDEMOVE)
skip += 2;
if (*stream & UCMDF_UPMOVE)
if (stream[0] & UCMDF_UPMOVE)
skip += 2;
if (*stream & UCMDF_ROLL)
if (stream[0] & UCMDF_ROLL)
skip += 2;
if (*stream & UCMDF_BUTTONS)
if (stream[0] & UCMDF_BUTTONS)
{
if (*++stream & MoreButtons)
AdvanceStream(stream, 1);
if (stream[0] & MoreButtons)
{
if (*++stream & MoreButtons)
AdvanceStream(stream, 1);
if (stream[0] & MoreButtons)
{
if (*++stream & MoreButtons)
++stream;
AdvanceStream(stream, 1);
if (stream[0] & MoreButtons)
AdvanceStream(stream, 1);
}
}
}
stream += skip;
AdvanceStream(stream, skip);
break;
}
else if (type == DEM_EMPTYUSERCMD)
@ -401,31 +623,29 @@ int SkipUserCmdMessage(uint8_t*& stream)
}
else
{
Net_SkipCommand(type, &stream);
Net_SkipCommand(type, stream);
}
}
return int(stream - start);
}
int ReadUserCmdMessage(uint8_t*& stream, int player, int tic)
void ReadUserCmdMessage(TArrayView<uint8_t>& stream, int player, int tic)
{
const int ticMod = tic % BACKUPTICS;
auto& curTic = ClientStates[player].Tics[ticMod];
usercmd_t& ticCmd = curTic.Command;
const uint8_t* start = stream;
const uint8_t* start = stream.Data();
// Skip until we reach the player command. Event data will get read off once the
// tick is actually executed.
int type;
while ((type = ReadInt8(&stream)) != DEM_USERCMD && type != DEM_EMPTYUSERCMD)
Net_SkipCommand(type, &stream);
while ((type = ReadInt8(stream)) != DEM_USERCMD && type != DEM_EMPTYUSERCMD)
Net_SkipCommand(type, stream);
// Subtract a byte to account for the fact the stream head is now sitting on the
// user command.
curTic.Data.SetData(start, int(stream - start - 1));
curTic.Data.SetData(start, int(stream.Data() - start - 1));
if (type == DEM_USERCMD)
{
@ -439,8 +659,6 @@ int ReadUserCmdMessage(uint8_t*& stream, int player, int tic)
else
memset(&ticCmd, 0, sizeof(ticCmd));
}
return int(stream - start);
}
void RunPlayerCommands(int player, int tic)
@ -449,14 +667,12 @@ void RunPlayerCommands(int player, int tic)
if (gametic % TicDup)
return;
int len;
auto& data = ClientStates[player].Tics[tic % BACKUPTICS].Data;
uint8_t* stream = data.GetData(&len);
if (stream != nullptr)
auto stream = data.GetTArrayView();
if (stream.Size())
{
uint8_t* end = stream + len;
while (stream < end)
Net_DoCommand(ReadInt8(&stream), &stream, player);
while (stream.Size() > 0)
Net_DoCommand(ReadInt8(stream), stream, player);
if (!demorecording)
data.SetData(nullptr, 0);
@ -469,22 +685,23 @@ uint8_t* streamPos = nullptr;
// Write the header of an IFF chunk and leave space
// for the length field.
void StartChunk(int id, uint8_t **stream)
void StartChunk(int id, TArrayView<uint8_t>& stream)
{
WriteInt32(id, stream);
streamPos = *stream;
*stream += 4;
streamPos = stream.Data();
AdvanceStream(stream, 4);
}
// Write the length field for the chunk and insert
// pad byte if the chunk is odd-sized.
void FinishChunk(uint8_t **stream)
void FinishChunk(TArrayView<uint8_t>& stream)
{
if (streamPos == nullptr)
return;
int len = int(*stream - streamPos - 4);
WriteInt32(len, &streamPos);
int len = int(stream.Data() - streamPos - 4);
auto streamPosView = TArrayView<uint8_t>(streamPos, 4);
WriteInt32(len, streamPosView);
if (len & 1)
WriteInt8(0, stream);
@ -493,8 +710,8 @@ void FinishChunk(uint8_t **stream)
// Skip past an unknown chunk. *stream should be
// pointing to the chunk's length field.
void SkipChunk(uint8_t **stream)
void SkipChunk(TArrayView<uint8_t>& stream)
{
int len = ReadInt32(stream);
*stream += len + (len & 1);
AdvanceStream(stream, len + (len & 1));
}