Browse Source

Validate length of incoming deltas

pull/7987/head
staphen 11 months ago committed by Stephen C. Wills
parent
commit
84546f85d4
  1. 28
      Source/encrypt.cpp
  2. 2
      Source/encrypt.h
  3. 95
      Source/msg.cpp

28
Source/encrypt.cpp

@ -21,9 +21,11 @@ namespace {
struct TDataInfo { struct TDataInfo {
std::byte *srcData; std::byte *srcData;
uint32_t srcOffset; uint32_t srcOffset;
uint32_t srcSize;
std::byte *destData; std::byte *destData;
uint32_t destOffset; uint32_t destOffset;
uint32_t size; size_t destSize;
bool error;
}; };
unsigned int PkwareBufferRead(char *buf, unsigned int *size, void *param) // NOLINT(readability-non-const-parameter) unsigned int PkwareBufferRead(char *buf, unsigned int *size, void *param) // NOLINT(readability-non-const-parameter)
@ -31,8 +33,8 @@ unsigned int PkwareBufferRead(char *buf, unsigned int *size, void *param) // NOL
auto *pInfo = reinterpret_cast<TDataInfo *>(param); auto *pInfo = reinterpret_cast<TDataInfo *>(param);
uint32_t sSize; uint32_t sSize;
if (*size >= pInfo->size - pInfo->srcOffset) { if (*size >= pInfo->srcSize - pInfo->srcOffset) {
sSize = pInfo->size - pInfo->srcOffset; sSize = pInfo->srcSize - pInfo->srcOffset;
} else { } else {
sSize = *size; sSize = *size;
} }
@ -47,6 +49,11 @@ void PkwareBufferWrite(char *buf, unsigned int *size, void *param) // NOLINT(rea
{ {
auto *pInfo = reinterpret_cast<TDataInfo *>(param); auto *pInfo = reinterpret_cast<TDataInfo *>(param);
pInfo->error = pInfo->error || pInfo->destOffset + *size > pInfo->destSize;
if (pInfo->error) {
return;
}
memcpy(pInfo->destData + pInfo->destOffset, buf, *size); memcpy(pInfo->destData + pInfo->destOffset, buf, *size);
pInfo->destOffset += *size; pInfo->destOffset += *size;
} }
@ -66,9 +73,11 @@ uint32_t PkwareCompress(std::byte *srcData, uint32_t size)
TDataInfo param; TDataInfo param;
param.srcData = srcData; param.srcData = srcData;
param.srcOffset = 0; param.srcOffset = 0;
param.srcSize = size;
param.destData = destData.get(); param.destData = destData.get();
param.destOffset = 0; param.destOffset = 0;
param.size = size; param.destSize = destSize;
param.error = false;
unsigned type = 0; unsigned type = 0;
unsigned dsize = 4096; unsigned dsize = 4096;
@ -82,7 +91,7 @@ uint32_t PkwareCompress(std::byte *srcData, uint32_t size)
return size; return size;
} }
void PkwareDecompress(std::byte *inBuff, uint32_t recvSize, int maxBytes) uint32_t PkwareDecompress(std::byte *inBuff, uint32_t recvSize, size_t maxBytes)
{ {
std::unique_ptr<char[]> ptr = std::make_unique<char[]>(CMP_BUFFER_SIZE); std::unique_ptr<char[]> ptr = std::make_unique<char[]>(CMP_BUFFER_SIZE);
std::unique_ptr<std::byte[]> outBuff { new std::byte[maxBytes] }; std::unique_ptr<std::byte[]> outBuff { new std::byte[maxBytes] };
@ -90,12 +99,19 @@ void PkwareDecompress(std::byte *inBuff, uint32_t recvSize, int maxBytes)
TDataInfo info; TDataInfo info;
info.srcData = inBuff; info.srcData = inBuff;
info.srcOffset = 0; info.srcOffset = 0;
info.srcSize = recvSize;
info.destData = outBuff.get(); info.destData = outBuff.get();
info.destOffset = 0; info.destOffset = 0;
info.size = recvSize; info.destSize = maxBytes;
info.error = false;
explode(PkwareBufferRead, PkwareBufferWrite, ptr.get(), &info); explode(PkwareBufferRead, PkwareBufferWrite, ptr.get(), &info);
if (info.error) {
return 0;
}
memcpy(inBuff, outBuff.get(), info.destOffset); memcpy(inBuff, outBuff.get(), info.destOffset);
return info.destOffset;
} }
} // namespace devilution } // namespace devilution

2
Source/encrypt.h

@ -11,6 +11,6 @@
namespace devilution { namespace devilution {
uint32_t PkwareCompress(std::byte *srcData, uint32_t size); uint32_t PkwareCompress(std::byte *srcData, uint32_t size);
void PkwareDecompress(std::byte *inBuff, uint32_t recvSize, int maxBytes); uint32_t PkwareDecompress(std::byte *inBuff, uint32_t recvSize, size_t maxBytes);
} // namespace devilution } // namespace devilution

95
Source/msg.cpp

@ -506,14 +506,18 @@ std::byte *DeltaExportItem(std::byte *dst, const TCmdPItem *src)
return dst; return dst;
} }
size_t DeltaImportItem(const std::byte *src, TCmdPItem *dst) const std::byte *DeltaImportItem(const std::byte *src, const std::byte *end, TCmdPItem *dst)
{ {
size_t size = 0; size_t size = 0;
for (int i = 0; i < MAXITEMS; i++, dst++) { for (int i = 0; i < MAXITEMS; i++, dst++) {
if (&src[size] >= end)
return nullptr;
if (src[size] == std::byte { 0xFF }) { if (src[size] == std::byte { 0xFF }) {
memset(dst, 0xFF, sizeof(TCmdPItem)); memset(dst, 0xFF, sizeof(TCmdPItem));
size++; size++;
} else { } else {
if (&src[size] + sizeof(TCmdPItem) > end)
return nullptr;
memcpy(dst, &src[size], sizeof(TCmdPItem)); memcpy(dst, &src[size], sizeof(TCmdPItem));
if (!IsItemDeltaValid(*dst)) if (!IsItemDeltaValid(*dst))
memset(dst, 0xFF, sizeof(TCmdPItem)); memset(dst, 0xFF, sizeof(TCmdPItem));
@ -521,7 +525,7 @@ size_t DeltaImportItem(const std::byte *src, TCmdPItem *dst)
} }
} }
return size; return src + size;
} }
std::byte *DeltaExportObject(std::byte *dst, const ankerl::unordered_dense::map<WorldTilePosition, DObjectStr> &src) std::byte *DeltaExportObject(std::byte *dst, const ankerl::unordered_dense::map<WorldTilePosition, DObjectStr> &src)
@ -536,11 +540,21 @@ std::byte *DeltaExportObject(std::byte *dst, const ankerl::unordered_dense::map<
return dst; return dst;
} }
const std::byte *DeltaImportObjects(const std::byte *src, ankerl::unordered_dense::map<WorldTilePosition, DObjectStr> &dst) const std::byte *DeltaImportObjects(const std::byte *src, const std::byte *end, ankerl::unordered_dense::map<WorldTilePosition, DObjectStr> &dst)
{ {
dst.clear(); dst.clear();
if (src == nullptr || src == end)
return nullptr;
uint8_t numDeltas = static_cast<uint8_t>(*src++); uint8_t numDeltas = static_cast<uint8_t>(*src++);
if (numDeltas > MAXOBJECTS)
return nullptr;
size_t numBytes = (sizeof(WorldTilePosition) + sizeof(_cmd_id)) * numDeltas;
if (src + numBytes > end)
return nullptr;
dst.reserve(numDeltas); dst.reserve(numDeltas);
for (unsigned i = 0; i < numDeltas; i++) { for (unsigned i = 0; i < numDeltas; i++) {
@ -566,20 +580,27 @@ std::byte *DeltaExportMonster(std::byte *dst, const DMonsterStr *src)
return dst; return dst;
} }
size_t DeltaImportMonster(const std::byte *src, DMonsterStr *dst) const std::byte *DeltaImportMonster(const std::byte *src, const std::byte *end, DMonsterStr *dst)
{ {
if (src == nullptr)
return nullptr;
size_t size = 0; size_t size = 0;
for (size_t i = 0; i < MaxMonsters; i++, dst++) { for (size_t i = 0; i < MaxMonsters; i++, dst++) {
if (&src[size] >= end)
return nullptr;
if (src[size] == std::byte { 0xFF }) { if (src[size] == std::byte { 0xFF }) {
memset(dst, 0xFF, sizeof(DMonsterStr)); memset(dst, 0xFF, sizeof(DMonsterStr));
size++; size++;
} else { } else {
if (&src[size] + sizeof(DMonsterStr) > end)
return nullptr;
memcpy(dst, &src[size], sizeof(DMonsterStr)); memcpy(dst, &src[size], sizeof(DMonsterStr));
size += sizeof(DMonsterStr); size += sizeof(DMonsterStr);
} }
} }
return size; return src + size;
} }
std::byte *DeltaExportSpawnedMonsters(std::byte *dst, const ankerl::unordered_dense::map<size_t, DSpawnedMonster> &spawnedMonsters) std::byte *DeltaExportSpawnedMonsters(std::byte *dst, const ankerl::unordered_dense::map<size_t, DSpawnedMonster> &spawnedMonsters)
@ -600,9 +621,19 @@ std::byte *DeltaExportSpawnedMonsters(std::byte *dst, const ankerl::unordered_de
return dst; return dst;
} }
const std::byte *DeltaImportSpawnedMonsters(const std::byte *src, ankerl::unordered_dense::map<size_t, DSpawnedMonster> &spawnedMonsters) const std::byte *DeltaImportSpawnedMonsters(const std::byte *src, const std::byte *end, ankerl::unordered_dense::map<size_t, DSpawnedMonster> &spawnedMonsters)
{ {
if (src == nullptr || src + sizeof(uint16_t) > end)
return nullptr;
uint16_t size = *reinterpret_cast<const uint16_t *>(src); uint16_t size = *reinterpret_cast<const uint16_t *>(src);
if (size > MaxMonsters)
return nullptr;
size_t requiredBytes = (sizeof(uint16_t) + sizeof(DSpawnedMonster)) * size;
if (src + requiredBytes > end)
return nullptr;
src += sizeof(uint16_t); src += sizeof(uint16_t);
for (size_t i = 0; i < size; i++) { for (size_t i = 0; i < size; i++) {
@ -646,13 +677,17 @@ std::byte *DeltaExportJunk(std::byte *dst)
return dst; return dst;
} }
void DeltaImportJunk(const std::byte *src) const std::byte *DeltaImportJunk(const std::byte *src, const std::byte *end)
{ {
for (int i = 0; i < MAXPORTAL; i++) { for (int i = 0; i < MAXPORTAL; i++) {
if (src >= end)
return nullptr;
if (*src == std::byte { 0xFF }) { if (*src == std::byte { 0xFF }) {
memset(&sgJunk.portal[i], 0xFF, sizeof(DPortal)); memset(&sgJunk.portal[i], 0xFF, sizeof(DPortal));
src++; src++;
} else { } else {
if (src + sizeof(DPortal) > end)
return nullptr;
memcpy(&sgJunk.portal[i], src, sizeof(DPortal)); memcpy(&sgJunk.portal[i], src, sizeof(DPortal));
src += sizeof(DPortal); src += sizeof(DPortal);
} }
@ -663,10 +698,15 @@ void DeltaImportJunk(const std::byte *src)
if (QuestsData[qidx].isSinglePlayerOnly && UseMultiplayerQuests()) { if (QuestsData[qidx].isSinglePlayerOnly && UseMultiplayerQuests()) {
continue; continue;
} }
if (src + sizeof(MultiQuests) > end) {
return nullptr;
}
memcpy(&sgJunk.quests[q], src, sizeof(MultiQuests)); memcpy(&sgJunk.quests[q], src, sizeof(MultiQuests));
src += sizeof(MultiQuests); src += sizeof(MultiQuests);
q++; q++;
} }
return src;
} }
uint32_t CompressData(std::byte *buffer, std::byte *end) uint32_t CompressData(std::byte *buffer, std::byte *end)
@ -684,28 +724,43 @@ uint32_t CompressData(std::byte *buffer, std::byte *end)
#endif #endif
} }
void DeltaImportData(_cmd_id cmd, uint32_t recvOffset) void DeltaImportData(_cmd_id cmd, uint32_t recvOffset, int pnum)
{ {
size_t deltaSize = recvOffset;
#ifdef USE_PKWARE #ifdef USE_PKWARE
if (sgRecvBuf[0] != std::byte { 0 }) if (sgRecvBuf[0] != std::byte { 0 }) {
PkwareDecompress(&sgRecvBuf[1], recvOffset, sizeof(sgRecvBuf) - 1); deltaSize = PkwareDecompress(&sgRecvBuf[1], deltaSize, sizeof(sgRecvBuf) - 1);
if (deltaSize == 0) {
Log("PKWare decompression failure, dropping player {}", pnum);
SNetDropPlayer(pnum, LEAVE_DROP);
return;
}
}
#endif #endif
const std::byte *src = &sgRecvBuf[1]; const std::byte *src = &sgRecvBuf[1];
const std::byte *end = src + deltaSize;
if (cmd == CMD_DLEVEL_JUNK) { if (cmd == CMD_DLEVEL_JUNK) {
DeltaImportJunk(src); src = DeltaImportJunk(src, end);
} else if (cmd == CMD_DLEVEL) { } else if (cmd == CMD_DLEVEL) {
uint8_t i = static_cast<uint8_t>(src[0]); uint8_t i = static_cast<uint8_t>(src[0]);
src += sizeof(uint8_t); src += sizeof(uint8_t);
DLevel &deltaLevel = GetDeltaLevel(i); DLevel &deltaLevel = GetDeltaLevel(i);
src += DeltaImportItem(src, deltaLevel.item); src = DeltaImportItem(src, end, deltaLevel.item);
src = DeltaImportObjects(src, deltaLevel.object); src = DeltaImportObjects(src, end, deltaLevel.object);
src += DeltaImportMonster(src, deltaLevel.monster); src = DeltaImportMonster(src, end, deltaLevel.monster);
src = DeltaImportSpawnedMonsters(src, deltaLevel.spawnedMonsters); src = DeltaImportSpawnedMonsters(src, end, deltaLevel.spawnedMonsters);
} else { } else {
app_fatal(StrCat("Unknown network message type: ", cmd)); app_fatal(StrCat("Unknown network message type: ", cmd));
} }
if (src == nullptr) {
Log("Received invalid deltas, dropping player {}", pnum);
SNetDropPlayer(pnum, LEAVE_DROP);
return;
}
sgbDeltaChunks++; sgbDeltaChunks++;
} }
@ -738,7 +793,7 @@ size_t OnLevelData(const TCmdPlrInfoHdr &message, size_t maxCmdSize, const Playe
sgdwRecvOffset = 0; sgdwRecvOffset = 0;
sgbRecvCmd = message.bCmd; sgbRecvCmd = message.bCmd;
} else if (sgbRecvCmd != message.bCmd || wOffset == 0) { } else if (sgbRecvCmd != message.bCmd || wOffset == 0) {
DeltaImportData(sgbRecvCmd, sgdwRecvOffset); DeltaImportData(sgbRecvCmd, sgdwRecvOffset, player.getId());
if (message.bCmd == CMD_DLEVEL_END) { if (message.bCmd == CMD_DLEVEL_END) {
sgbDeltaChunks = MAX_CHUNKS - 1; sgbDeltaChunks = MAX_CHUNKS - 1;
sgbRecvCmd = CMD_DLEVEL_END; sgbRecvCmd = CMD_DLEVEL_END;
@ -748,8 +803,14 @@ size_t OnLevelData(const TCmdPlrInfoHdr &message, size_t maxCmdSize, const Playe
sgbRecvCmd = message.bCmd; sgbRecvCmd = message.bCmd;
} }
if (sgdwRecvOffset + wBytes > sizeof(sgRecvBuf)) {
Log("Received too many deltas, dropping player {}", player.getId());
SNetDropPlayer(player.getId(), LEAVE_DROP);
return wBytes + sizeof(message);
}
assert(wOffset == sgdwRecvOffset); assert(wOffset == sgdwRecvOffset);
memcpy(&sgRecvBuf[wOffset], &message + 1, wBytes); memcpy(&sgRecvBuf[sgdwRecvOffset], &message + 1, wBytes);
sgdwRecvOffset += wBytes; sgdwRecvOffset += wBytes;
return wBytes + sizeof(message); return wBytes + sizeof(message);
} }

Loading…
Cancel
Save