aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/FileStream.cpp2
-rw-r--r--src/SBaseCommon.cpp16
-rw-r--r--src/SBaseFileTable.cpp268
-rw-r--r--src/SFileGetFileInfo.cpp4
-rw-r--r--src/SFileOpenArchive.cpp10
-rw-r--r--src/SFileReadFile.cpp7
-rw-r--r--src/huffman/huff.cpp50
-rw-r--r--src/huffman/huff.h4
-rw-r--r--src/sparse/sparse.cpp5
-rwxr-xr-xtest/StormTest.cpp40
10 files changed, 259 insertions, 147 deletions
diff --git a/src/FileStream.cpp b/src/FileStream.cpp
index bc5618e..b66098c 100644
--- a/src/FileStream.cpp
+++ b/src/FileStream.cpp
@@ -1198,7 +1198,7 @@ static bool FlatStream_LoadBitmap(TBlockStream * pStream)
BSWAP_ARRAY32_UNSIGNED((LPDWORD)(&Footer), sizeof(FILE_BITMAP_FOOTER));
// Verify if there is actually a footer
- if(Footer.Signature == ID_FILE_BITMAP_FOOTER && Footer.Version == 0x03)
+ if(Footer.Signature == ID_FILE_BITMAP_FOOTER && Footer.Version == 0x03 && Footer.BlockSize != 0)
{
// Get the offset of the bitmap, number of blocks and size of the bitmap
ByteOffset = MAKE_OFFSET64(Footer.MapOffsetHi, Footer.MapOffsetLo);
diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp
index 587efe4..0de7864 100644
--- a/src/SBaseCommon.cpp
+++ b/src/SBaseCommon.cpp
@@ -1027,20 +1027,16 @@ void * LoadMpqTable(
if(ByteOffset == SFILE_INVALID_POS)
FileStream_GetPos(ha->pStream, &ByteOffset);
- // On archives v 1.0, hash table and block table can go beyond EOF.
+ // The hash table and block table can go beyond EOF.
// Storm.dll reads as much as possible, then fills the missing part with zeros.
// Abused by Spazzler map protector which sets hash table size to 0x00100000
// Abused by NP_Protect in MPQs v4 as well
- if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
+ FileStream_GetSize(ha->pStream, &FileSize);
+ if((ByteOffset + dwBytesToRead) > FileSize)
{
- // Cut the table size
- FileStream_GetSize(ha->pStream, &FileSize);
- if((ByteOffset + dwBytesToRead) > FileSize)
- {
- // Fill the extra data with zeros
- dwBytesToRead = (DWORD)(FileSize - ByteOffset);
- memset(pbMpqTable + dwBytesToRead, 0, (dwTableSize - dwBytesToRead));
- }
+ // Fill the extra data with zeros
+ dwBytesToRead = (DWORD)(FileSize - ByteOffset);
+ memset(pbMpqTable + dwBytesToRead, 0, (dwTableSize - dwBytesToRead));
}
// Give the caller information that the table was cut
diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp
index a222ac5..0461be2 100644
--- a/src/SBaseFileTable.cpp
+++ b/src/SBaseFileTable.cpp
@@ -63,8 +63,8 @@ struct TMPQBits
{
static TMPQBits * Create(DWORD NumberOfBits, BYTE FillValue);
- void GetBits(unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, int nResultSize);
- void SetBits(unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, int nResultSize);
+ DWORD GetBits(unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, unsigned int nResultSize);
+ DWORD SetBits(unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, unsigned int nResultSize);
static const USHORT SetBitsMask[]; // Bit mask for each number of bits (0-8)
@@ -94,11 +94,11 @@ TMPQBits * TMPQBits::Create(
return pBitArray;
}
-void TMPQBits::GetBits(
+DWORD TMPQBits::GetBits(
unsigned int nBitPosition,
unsigned int nBitLength,
void * pvBuffer,
- int nResultByteSize)
+ unsigned int nResultByteSize)
{
unsigned char * pbBuffer = (unsigned char *)pvBuffer;
unsigned int nBytePosition0 = (nBitPosition / 8);
@@ -107,12 +107,17 @@ void TMPQBits::GetBits(
unsigned int nBitOffset = (nBitPosition & 0x07);
unsigned char BitBuffer;
- // Keep compilers happy for platforms where nResultByteSize is not used
- STORMLIB_UNUSED(nResultByteSize);
+ // Check for bit overflow
+ if(nBitPosition + nBitLength < nBitPosition)
+ return ERROR_BUFFER_OVERFLOW;
+ if(nBitPosition + nBitLength > NumberOfBits)
+ return ERROR_BUFFER_OVERFLOW;
+ if(nByteLength > nResultByteSize)
+ return ERROR_BUFFER_OVERFLOW;
#ifdef _DEBUG
// Check if the target is properly zeroed
- for(int i = 0; i < nResultByteSize; i++)
+ for(unsigned int i = 0; i < nResultByteSize; i++)
assert(pbBuffer[i] == 0);
#endif
@@ -157,13 +162,14 @@ void TMPQBits::GetBits(
*pbBuffer &= (0x01 << nBitLength) - 1;
}
+ return ERROR_SUCCESS;
}
-void TMPQBits::SetBits(
+DWORD TMPQBits::SetBits(
unsigned int nBitPosition,
unsigned int nBitLength,
void * pvBuffer,
- int nResultByteSize)
+ unsigned int nResultByteSize)
{
unsigned char * pbBuffer = (unsigned char *)pvBuffer;
unsigned int nBytePosition = (nBitPosition / 8);
@@ -175,6 +181,14 @@ void TMPQBits::SetBits(
// Keep compilers happy for platforms where nResultByteSize is not used
STORMLIB_UNUSED(nResultByteSize);
+ // Check for bit overflow
+ if(nBitPosition + nBitLength < nBitPosition)
+ return ERROR_BUFFER_OVERFLOW;
+ if(nBitPosition + nBitLength > NumberOfBits)
+ return ERROR_BUFFER_OVERFLOW;
+ if(nBitLength / 8 > nResultByteSize)
+ return ERROR_BUFFER_OVERFLOW;
+
#ifndef STORMLIB_LITTLE_ENDIAN
// Adjust the buffer pointer for big endian platforms
pbBuffer += (nResultByteSize - 1);
@@ -223,6 +237,7 @@ void TMPQBits::SetBits(
Elements[nBytePosition] = (BYTE)((Elements[nBytePosition] & ~AndMask) | BitBuffer);
}
}
+ return ERROR_SUCCESS;
}
void GetMPQBits(TMPQBits * pBits, unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, int nResultByteSize)
@@ -559,6 +574,10 @@ DWORD ConvertMpqHeaderToFormat4(
// Fill the rest of the header with zeros
memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V2, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V2);
+ // Check position of the hi-block table
+ if(pHeader->HiBlockTablePos64 > FileSize)
+ return ERROR_FILE_CORRUPT;
+
// Calculate the expected hash table size
pHeader->HashTableSize64 = (pHeader->dwHashTableSize * sizeof(TMPQHash));
HashTablePos64 = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos);
@@ -602,6 +621,9 @@ DWORD ConvertMpqHeaderToFormat4(
pHeader->BetTablePos64 = 0;
}
+ // Fixup malformed MPQ header sizes
+ pHeader->dwHeaderSize = STORMLIB_MIN(pHeader->dwHeaderSize, MPQ_HEADER_SIZE_V3);
+
//
// We need to calculate the compressed size of each table. We assume the following order:
// 1) HET table
@@ -620,6 +642,8 @@ DWORD ConvertMpqHeaderToFormat4(
// Size of the hi-block table
if(pHeader->HiBlockTablePos64)
{
+ if(pHeader->HiBlockTablePos64 > FileSize)
+ return ERROR_FILE_CORRUPT;
pHeader->HiBlockTableSize64 = MaxOffset - pHeader->HiBlockTablePos64;
MaxOffset = pHeader->HiBlockTablePos64;
}
@@ -627,6 +651,8 @@ DWORD ConvertMpqHeaderToFormat4(
// Size of the block table
if(BlockTablePos64)
{
+ if(BlockTablePos64 > FileSize)
+ return ERROR_FILE_CORRUPT;
pHeader->BlockTableSize64 = MaxOffset - BlockTablePos64;
MaxOffset = BlockTablePos64;
}
@@ -634,6 +660,8 @@ DWORD ConvertMpqHeaderToFormat4(
// Size of the hash table
if(HashTablePos64)
{
+ if(HashTablePos64 > FileSize)
+ return ERROR_FILE_CORRUPT;
pHeader->HashTableSize64 = MaxOffset - HashTablePos64;
MaxOffset = HashTablePos64;
}
@@ -641,6 +669,8 @@ DWORD ConvertMpqHeaderToFormat4(
// Size of the BET table
if(pHeader->BetTablePos64)
{
+ if(pHeader->BetTablePos64 > FileSize)
+ return ERROR_FILE_CORRUPT;
pHeader->BetTableSize64 = MaxOffset - pHeader->BetTablePos64;
MaxOffset = pHeader->BetTablePos64;
}
@@ -648,6 +678,8 @@ DWORD ConvertMpqHeaderToFormat4(
// Size of the HET table
if(pHeader->HetTablePos64)
{
+ if(pHeader->HetTablePos64 > FileSize)
+ return ERROR_FILE_CORRUPT;
pHeader->HetTableSize64 = MaxOffset - pHeader->HetTablePos64;
// MaxOffset = pHeader->HetTablePos64;
}
@@ -660,7 +692,6 @@ DWORD ConvertMpqHeaderToFormat4(
// Verify header MD5. Header MD5 is calculated from the MPQ header since the 'MPQ\x1A'
// signature until the position of header MD5 at offset 0xC0
-
// Apparently, Starcraft II only accepts MPQ headers where the MPQ header hash matches
// If MD5 doesn't match, we ignore this offset. We also ignore it if there's no MD5 at all
if(!IsValidMD5(pHeader->MD5_MpqHeader))
@@ -671,6 +702,9 @@ DWORD ConvertMpqHeaderToFormat4(
// Byteswap after header MD5 is verified
BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_4);
+ // Fixup malformed MPQ header sizes
+ pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V4;
+
// HiBlockTable must be 0 for archives under 4GB
if((pHeader->ArchiveSize64 >> 0x20) == 0 && pHeader->HiBlockTablePos64 != 0)
return ERROR_FAKE_MPQ_HEADER;
@@ -759,9 +793,12 @@ static bool IsValidHashEntry1(TMPQArchive * ha, TMPQHash * pHash, TMPQBlock * pB
pBlock = pBlockTable + MPQ_BLOCK_INDEX(pHash);
// Check whether this is an existing file
- // Also we do not allow to be file size greater than 2GB
- if((pBlock->dwFlags & MPQ_FILE_EXISTS) && (pBlock->dwFSize & 0x80000000) == 0)
+ if(pBlock->dwFlags & MPQ_FILE_EXISTS)
{
+ // We don't allow to be file size greater than 2GB in malformed archives
+ if((ha->dwFlags & MPQ_FLAG_MALFORMED) && (pBlock->dwFSize >= 0x80000000))
+ return false;
+
// The begin of the file must be within the archive
ByteOffset = FileOffsetFromMpqOffset(ha, pBlock->dwFilePos);
return (ByteOffset < ha->FileSize);
@@ -1604,15 +1641,16 @@ static DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName)
DWORD dwFileIndex = 0;
// Get the file index
- pHetTable->pBetIndexes->GetBits(pHetTable->dwIndexSizeTotal * Index,
- pHetTable->dwIndexSize,
- &dwFileIndex,
- sizeof(DWORD));
-
- // Verify the FileNameHash against the entry in the table of name hashes
- if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].FileNameHash == FileNameHash)
+ if(pHetTable->pBetIndexes->GetBits(pHetTable->dwIndexSizeTotal * Index,
+ pHetTable->dwIndexSize,
+ &dwFileIndex,
+ sizeof(DWORD)) == ERROR_SUCCESS)
{
- return dwFileIndex;
+ // Verify the FileNameHash against the entry in the table of name hashes
+ if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].FileNameHash == FileNameHash)
+ {
+ return dwFileIndex;
+ }
}
}
@@ -1643,6 +1681,16 @@ void FreeHetTable(TMPQHetTable * pHetTable)
//-----------------------------------------------------------------------------
// Support for BET table
+static bool VerifyBetHeaderSize(TMPQArchive * /* ha */, TMPQBetHeader * pBetHeader)
+{
+ LPBYTE pbSrcData = (LPBYTE)(pBetHeader + 1);
+ LPBYTE pbSrcEnd = (LPBYTE)(pBetHeader) + pBetHeader->dwTableSize;
+
+ // Move past the flags
+ pbSrcData = pbSrcData + (pBetHeader->dwFlagCount * sizeof(DWORD)) + (pBetHeader->dwEntryCount * pBetHeader->dwTableEntrySize) / 8;
+ return (pbSrcData <= pbSrcEnd);
+}
+
static void CreateBetHeader(
TMPQArchive * ha,
TMPQBetHeader * pBetHeader)
@@ -1776,68 +1824,72 @@ static TMPQBetTable * TranslateBetTable(
// Note that if it's not, it is not a problem
//assert(pBetHeader->dwEntryCount == ha->pHetTable->dwEntryCount);
- // Create translated table
- pBetTable = CreateBetTable(pBetHeader->dwEntryCount);
- if(pBetTable != NULL)
+ // Verify an obviously-wrong values
+ if(VerifyBetHeaderSize(ha, pBetHeader))
{
- // Copy the variables from the header to the BetTable
- pBetTable->dwTableEntrySize = pBetHeader->dwTableEntrySize;
- pBetTable->dwBitIndex_FilePos = pBetHeader->dwBitIndex_FilePos;
- pBetTable->dwBitIndex_FileSize = pBetHeader->dwBitIndex_FileSize;
- pBetTable->dwBitIndex_CmpSize = pBetHeader->dwBitIndex_CmpSize;
- pBetTable->dwBitIndex_FlagIndex = pBetHeader->dwBitIndex_FlagIndex;
- pBetTable->dwBitIndex_Unknown = pBetHeader->dwBitIndex_Unknown;
- pBetTable->dwBitCount_FilePos = pBetHeader->dwBitCount_FilePos;
- pBetTable->dwBitCount_FileSize = pBetHeader->dwBitCount_FileSize;
- pBetTable->dwBitCount_CmpSize = pBetHeader->dwBitCount_CmpSize;
- pBetTable->dwBitCount_FlagIndex = pBetHeader->dwBitCount_FlagIndex;
- pBetTable->dwBitCount_Unknown = pBetHeader->dwBitCount_Unknown;
-
- // Since we don't know what the "unknown" is, we'll assert when it's zero
- assert(pBetTable->dwBitCount_Unknown == 0);
-
- // Allocate array for flags
- if(pBetHeader->dwFlagCount != 0)
+ // Create translated table
+ pBetTable = CreateBetTable(pBetHeader->dwEntryCount);
+ if(pBetTable != NULL)
{
- // Allocate array for file flags and load it
- pBetTable->pFileFlags = STORM_ALLOC(DWORD, pBetHeader->dwFlagCount);
- if(pBetTable->pFileFlags != NULL)
+ // Copy the variables from the header to the BetTable
+ pBetTable->dwTableEntrySize = pBetHeader->dwTableEntrySize;
+ pBetTable->dwBitIndex_FilePos = pBetHeader->dwBitIndex_FilePos;
+ pBetTable->dwBitIndex_FileSize = pBetHeader->dwBitIndex_FileSize;
+ pBetTable->dwBitIndex_CmpSize = pBetHeader->dwBitIndex_CmpSize;
+ pBetTable->dwBitIndex_FlagIndex = pBetHeader->dwBitIndex_FlagIndex;
+ pBetTable->dwBitIndex_Unknown = pBetHeader->dwBitIndex_Unknown;
+ pBetTable->dwBitCount_FilePos = pBetHeader->dwBitCount_FilePos;
+ pBetTable->dwBitCount_FileSize = pBetHeader->dwBitCount_FileSize;
+ pBetTable->dwBitCount_CmpSize = pBetHeader->dwBitCount_CmpSize;
+ pBetTable->dwBitCount_FlagIndex = pBetHeader->dwBitCount_FlagIndex;
+ pBetTable->dwBitCount_Unknown = pBetHeader->dwBitCount_Unknown;
+
+ // Since we don't know what the "unknown" is, we'll assert when it's zero
+ assert(pBetTable->dwBitCount_Unknown == 0);
+
+ // Allocate array for flags
+ if(pBetHeader->dwFlagCount != 0)
{
- LengthInBytes = pBetHeader->dwFlagCount * sizeof(DWORD);
- memcpy(pBetTable->pFileFlags, pbSrcData, LengthInBytes);
- BSWAP_ARRAY32_UNSIGNED(pBetTable->pFileFlags, LengthInBytes);
- pbSrcData += LengthInBytes;
+ // Allocate array for file flags and load it
+ pBetTable->pFileFlags = STORM_ALLOC(DWORD, pBetHeader->dwFlagCount);
+ if(pBetTable->pFileFlags != NULL)
+ {
+ LengthInBytes = pBetHeader->dwFlagCount * sizeof(DWORD);
+ memcpy(pBetTable->pFileFlags, pbSrcData, LengthInBytes);
+ BSWAP_ARRAY32_UNSIGNED(pBetTable->pFileFlags, LengthInBytes);
+ pbSrcData += LengthInBytes;
+ }
+
+ // Save the number of flags
+ pBetTable->dwFlagCount = pBetHeader->dwFlagCount;
}
- // Save the number of flags
- pBetTable->dwFlagCount = pBetHeader->dwFlagCount;
- }
+ // Load the bit-based file table
+ pBetTable->pFileTable = TMPQBits::Create(pBetTable->dwTableEntrySize * pBetHeader->dwEntryCount, 0);
+ if(pBetTable->pFileTable != NULL)
+ {
+ LengthInBytes = (pBetTable->pFileTable->NumberOfBits + 7) / 8;
+ memcpy(pBetTable->pFileTable->Elements, pbSrcData, LengthInBytes);
+ pbSrcData += LengthInBytes;
+ }
- // Load the bit-based file table
- pBetTable->pFileTable = TMPQBits::Create(pBetTable->dwTableEntrySize * pBetHeader->dwEntryCount, 0);
- if(pBetTable->pFileTable != NULL)
- {
- LengthInBytes = (pBetTable->pFileTable->NumberOfBits + 7) / 8;
- memcpy(pBetTable->pFileTable->Elements, pbSrcData, LengthInBytes);
- pbSrcData += LengthInBytes;
- }
+ // Fill the sizes of BET hash
+ pBetTable->dwBitTotal_NameHash2 = pBetHeader->dwBitTotal_NameHash2;
+ pBetTable->dwBitExtra_NameHash2 = pBetHeader->dwBitExtra_NameHash2;
+ pBetTable->dwBitCount_NameHash2 = pBetHeader->dwBitCount_NameHash2;
- // Fill the sizes of BET hash
- pBetTable->dwBitTotal_NameHash2 = pBetHeader->dwBitTotal_NameHash2;
- pBetTable->dwBitExtra_NameHash2 = pBetHeader->dwBitExtra_NameHash2;
- pBetTable->dwBitCount_NameHash2 = pBetHeader->dwBitCount_NameHash2;
+ // Create and load the array of BET hashes
+ pBetTable->pNameHashes = TMPQBits::Create(pBetTable->dwBitTotal_NameHash2 * pBetHeader->dwEntryCount, 0);
+ if(pBetTable->pNameHashes != NULL)
+ {
+ LengthInBytes = (pBetTable->pNameHashes->NumberOfBits + 7) / 8;
+ memcpy(pBetTable->pNameHashes->Elements, pbSrcData, LengthInBytes);
+ // pbSrcData += LengthInBytes;
+ }
- // Create and load the array of BET hashes
- pBetTable->pNameHashes = TMPQBits::Create(pBetTable->dwBitTotal_NameHash2 * pBetHeader->dwEntryCount, 0);
- if(pBetTable->pNameHashes != NULL)
- {
- LengthInBytes = (pBetTable->pNameHashes->NumberOfBits + 7) / 8;
- memcpy(pBetTable->pNameHashes->Elements, pbSrcData, LengthInBytes);
-// pbSrcData += LengthInBytes;
+ // Dump both tables
+// DumpHetAndBetTable(ha->pHetTable, pBetTable);
}
-
- // Dump both tables
-// DumpHetAndBetTable(ha->pHetTable, pBetTable);
}
}
}
@@ -2599,7 +2651,7 @@ static DWORD BuildFileTable_HetBet(TMPQArchive * ha)
TMPQBits * pBitArray;
DWORD dwBitPosition = 0;
DWORD i;
- DWORD dwErrCode = ERROR_FILE_CORRUPT;
+ DWORD dwErrCode = ERROR_SUCCESS;
// Load the BET table from the MPQ
pBetTable = LoadBetTable(ha);
@@ -2622,10 +2674,16 @@ static DWORD BuildFileTable_HetBet(TMPQArchive * ha)
if(pHetTable->pNameHashes[i] != HET_ENTRY_FREE)
{
// Load the index to the BET table
- pHetTable->pBetIndexes->GetBits(pHetTable->dwIndexSizeTotal * i,
- pHetTable->dwIndexSize,
- &dwFileIndex,
- 4);
+ dwErrCode = pHetTable->pBetIndexes->GetBits(pHetTable->dwIndexSizeTotal * i,
+ pHetTable->dwIndexSize,
+ &dwFileIndex,
+ 4);
+ if(dwErrCode != ERROR_SUCCESS)
+ {
+ FreeBetTable(pBetTable);
+ return ERROR_FILE_CORRUPT;
+ }
+
// Overflow test
if(dwFileIndex < pBetTable->dwEntryCount)
{
@@ -2633,10 +2691,15 @@ static DWORD BuildFileTable_HetBet(TMPQArchive * ha)
ULONGLONG NameHash2 = 0;
// Load the BET hash
- pBetTable->pNameHashes->GetBits(pBetTable->dwBitTotal_NameHash2 * dwFileIndex,
- pBetTable->dwBitCount_NameHash2,
- &NameHash2,
- 8);
+ dwErrCode = pBetTable->pNameHashes->GetBits(pBetTable->dwBitTotal_NameHash2 * dwFileIndex,
+ pBetTable->dwBitCount_NameHash2,
+ &NameHash2,
+ 8);
+ if(dwErrCode != ERROR_SUCCESS)
+ {
+ FreeBetTable(pBetTable);
+ return ERROR_FILE_CORRUPT;
+ }
// Combine both part of the name hash and put it to the file table
pFileEntry = ha->pFileTable + dwFileIndex;
@@ -2653,31 +2716,35 @@ static DWORD BuildFileTable_HetBet(TMPQArchive * ha)
DWORD dwFlagIndex = 0;
// Read the file position
- pBitArray->GetBits(dwBitPosition + pBetTable->dwBitIndex_FilePos,
- pBetTable->dwBitCount_FilePos,
- &pFileEntry->ByteOffset,
- 8);
+ if((dwErrCode = pBitArray->GetBits(dwBitPosition + pBetTable->dwBitIndex_FilePos,
+ pBetTable->dwBitCount_FilePos,
+ &pFileEntry->ByteOffset,
+ 8)) != ERROR_SUCCESS)
+ break;
// Read the file size
- pBitArray->GetBits(dwBitPosition + pBetTable->dwBitIndex_FileSize,
- pBetTable->dwBitCount_FileSize,
- &pFileEntry->dwFileSize,
- 4);
+ if((dwErrCode = pBitArray->GetBits(dwBitPosition + pBetTable->dwBitIndex_FileSize,
+ pBetTable->dwBitCount_FileSize,
+ &pFileEntry->dwFileSize,
+ 4)) != ERROR_SUCCESS)
+ break;
// Read the compressed size
- pBitArray->GetBits(dwBitPosition + pBetTable->dwBitIndex_CmpSize,
- pBetTable->dwBitCount_CmpSize,
- &pFileEntry->dwCmpSize,
- 4);
-
+ if((dwErrCode = pBitArray->GetBits(dwBitPosition + pBetTable->dwBitIndex_CmpSize,
+ pBetTable->dwBitCount_CmpSize,
+ &pFileEntry->dwCmpSize,
+ 4)) != ERROR_SUCCESS)
+ break;
// Read the flag index
if(pBetTable->dwFlagCount != 0)
{
- pBitArray->GetBits(dwBitPosition + pBetTable->dwBitIndex_FlagIndex,
- pBetTable->dwBitCount_FlagIndex,
- &dwFlagIndex,
- 4);
+ if((dwErrCode = pBitArray->GetBits(dwBitPosition + pBetTable->dwBitIndex_FlagIndex,
+ pBetTable->dwBitCount_FlagIndex,
+ &dwFlagIndex,
+ 4)) != ERROR_SUCCESS)
+ break;
+
pFileEntry->dwFlags = pBetTable->pFileFlags[dwFlagIndex];
}
@@ -2692,13 +2759,11 @@ static DWORD BuildFileTable_HetBet(TMPQArchive * ha)
// Set the current size of the file table
FreeBetTable(pBetTable);
- dwErrCode = ERROR_SUCCESS;
}
else
{
dwErrCode = ERROR_FILE_CORRUPT;
}
-
return dwErrCode;
}
@@ -3100,6 +3165,7 @@ DWORD SaveMPQTables(TMPQArchive * ha)
CalculateDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader);
// Write the MPQ header to the file
+ assert(pHeader->dwHeaderSize <= sizeof(SaveMpqHeader));
memcpy(&SaveMpqHeader, pHeader, pHeader->dwHeaderSize);
BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_1);
BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_2);
diff --git a/src/SFileGetFileInfo.cpp b/src/SFileGetFileInfo.cpp
index 20f5e75..1746fa0 100644
--- a/src/SFileGetFileInfo.cpp
+++ b/src/SFileGetFileInfo.cpp
@@ -428,6 +428,8 @@ bool WINAPI SFileGetFileInfo(
return GetInfo(pvFileInfo, cbFileInfo, &dwInt32Value, sizeof(DWORD), pcbLengthNeeded);
case SFileInfoFileIndex:
+ if(hf->ha == NULL)
+ return GetInfo_ReturnError(ERROR_INVALID_PARAMETER);
dwInt32Value = (DWORD)(pFileEntry - hf->ha->pFileTable);
return GetInfo(pvFileInfo, cbFileInfo, &dwInt32Value, sizeof(DWORD), pcbLengthNeeded);
@@ -450,6 +452,8 @@ bool WINAPI SFileGetFileInfo(
return GetInfo(pvFileInfo, cbFileInfo, &hf->dwFileKey, sizeof(DWORD), pcbLengthNeeded);
case SFileInfoEncryptionKeyRaw:
+ if(pFileEntry == NULL)
+ return GetInfo_ReturnError(ERROR_INVALID_PARAMETER);
dwInt32Value = hf->dwFileKey;
if(pFileEntry->dwFlags & MPQ_FILE_KEY_V2)
dwInt32Value = (dwInt32Value ^ pFileEntry->dwFileSize) - (DWORD)hf->MpqFilePos;
diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp
index a97ecea..587aa96 100644
--- a/src/SFileOpenArchive.cpp
+++ b/src/SFileOpenArchive.cpp
@@ -501,10 +501,14 @@ bool WINAPI SFileOpenArchive(
break;
}
- // Set the size of file sector
- ha->dwSectorSize = (0x200 << ha->pHeader->wSectorSize);
+ // Set the size of file sector. Be sure to check for integer overflow
+ if((ha->dwSectorSize = (0x200 << ha->pHeader->wSectorSize)) == 0)
+ dwErrCode = ERROR_FILE_CORRUPT;
+ }
- // Verify if any of the tables doesn't start beyond the end of the file
+ // Verify if any of the tables doesn't start beyond the end of the file
+ if(dwErrCode == ERROR_SUCCESS)
+ {
dwErrCode = VerifyMpqTablePositions(ha, FileSize);
}
diff --git a/src/SFileReadFile.cpp b/src/SFileReadFile.cpp
index 8ab5f48..180d428 100644
--- a/src/SFileReadFile.cpp
+++ b/src/SFileReadFile.cpp
@@ -261,6 +261,8 @@ static DWORD ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFileP
// If the file sector is not loaded yet, do it
if(hf->dwSectorOffs != 0)
{
+ DWORD cbRawData = hf->dwDataSize;
+
// Is the file compressed?
if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
{
@@ -268,11 +270,14 @@ static DWORD ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFileP
pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize);
if(pbCompressed == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Redirect reading
pbRawData = pbCompressed;
+ cbRawData = pFileEntry->dwCmpSize;
}
// Load the raw (compressed, encrypted) data
- if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize))
+ if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, cbRawData))
{
STORM_FREE(pbCompressed);
return GetLastError();
diff --git a/src/huffman/huff.cpp b/src/huffman/huff.cpp
index 1b81017..bbb8118 100644
--- a/src/huffman/huff.cpp
+++ b/src/huffman/huff.cpp
@@ -22,6 +22,11 @@
#include "huff.h"
//-----------------------------------------------------------------------------
+// Local defined
+
+#define HUFF_DECOMPRESS_ERROR 0x1FF
+
+//-----------------------------------------------------------------------------
// Table of byte-to-weight values
// Table for (de)compression. Every compression type has 258 entries
@@ -270,46 +275,50 @@ TInputStream::TInputStream(void * pvInBuffer, size_t cbInBuffer)
}
// Gets one bit from input stream
-unsigned int TInputStream::Get1Bit()
+bool TInputStream::Get1Bit(unsigned int & BitValue)
{
- unsigned int OneBit = 0;
-
// Ensure that the input stream is reloaded, if there are no bits left
if(BitCount == 0)
{
+ // Buffer overflow check
+ if(pbInBuffer >= pbInBufferEnd)
+ return false;
+
// Refill the bit buffer
BitBuffer = *pbInBuffer++;
BitCount = 8;
}
// Copy the bit from bit buffer to the variable
- OneBit = (BitBuffer & 0x01);
+ BitValue = (BitBuffer & 0x01);
BitBuffer >>= 1;
BitCount--;
-
- return OneBit;
+ return true;
}
// Gets the whole byte from the input stream.
-unsigned int TInputStream::Get8Bits()
+bool TInputStream::Get8Bits(unsigned int & ByteValue)
{
unsigned int dwReloadByte = 0;
- unsigned int dwOneByte = 0;
// If there is not enough bits to get the value,
// we have to add 8 more bits from the input buffer
if(BitCount < 8)
{
+ // Buffer overflow check
+ if(pbInBuffer >= pbInBufferEnd)
+ return false;
+
dwReloadByte = *pbInBuffer++;
BitBuffer |= dwReloadByte << BitCount;
BitCount += 8;
}
// Return the lowest 8 its
- dwOneByte = (BitBuffer & 0xFF);
+ ByteValue = (BitBuffer & 0xFF);
BitBuffer >>= 8;
BitCount -= 8;
- return dwOneByte;
+ return true;
}
// Gets 7 bits from the stream. DOES NOT remove the bits from input stream
@@ -344,6 +353,10 @@ void TInputStream::SkipBits(unsigned int dwBitsToSkip)
// we have to add 8 more bits from the input buffer
if(BitCount < dwBitsToSkip)
{
+ // Buffer overflow check
+ if(pbInBuffer >= pbInBufferEnd)
+ return;
+
dwReloadByte = *pbInBuffer++;
BitBuffer |= dwReloadByte << BitCount;
BitCount += 8;
@@ -726,7 +739,7 @@ unsigned int THuffmannTree::DecodeOneByte(TInputStream * is)
{
// Just a sanity check
if(pFirst == LIST_HEAD())
- return 0x1FF;
+ return HUFF_DECOMPRESS_ERROR;
// We don't have the quick-link item, we need to parse the tree from its root
pItem = pFirst;
@@ -735,9 +748,14 @@ unsigned int THuffmannTree::DecodeOneByte(TInputStream * is)
// Step down the tree until we find a terminal item
while(pItem->pChildLo != NULL)
{
+ unsigned int BitValue = 0;
+
// If the next bit in the compressed stream is set, we get the higher-weight
// child. Otherwise, get the lower-weight child.
- pItem = is->Get1Bit() ? pItem->pChildLo->pPrev : pItem->pChildLo;
+ if(!is->Get1Bit(BitValue))
+ return HUFF_DECOMPRESS_ERROR;
+
+ pItem = BitValue ? pItem->pChildLo->pPrev : pItem->pChildLo;
BitCount++;
// If the number of loaded bits reached 7,
@@ -852,7 +870,8 @@ unsigned int THuffmannTree::Decompress(void * pvOutBuffer, unsigned int cbOutLen
return 0;
// Get the compression type from the input stream
- CompressionType = is->Get8Bits();
+ if(!is->Get8Bits(CompressionType))
+ return 0;
bIsCmp0 = (CompressionType == 0) ? 1 : 0;
// Build the Huffman tree
@@ -863,14 +882,15 @@ unsigned int THuffmannTree::Decompress(void * pvOutBuffer, unsigned int cbOutLen
while((DecompressedValue = DecodeOneByte(is)) != 0x100)
{
// Did an error occur?
- if(DecompressedValue == 0x1FF) // An error occurred
+ if(DecompressedValue == HUFF_DECOMPRESS_ERROR)
return 0;
// Huffman tree needs to be modified
if(DecompressedValue == 0x101)
{
// The decompressed byte is stored in the next 8 bits
- DecompressedValue = is->Get8Bits();
+ if(!is->Get8Bits(DecompressedValue))
+ return 0;
if(!InsertNewBranchAndRebalance(pLast->DecompressedValue, DecompressedValue))
return 0;
diff --git a/src/huffman/huff.h b/src/huffman/huff.h
index cf1ca4c..b2c6370 100644
--- a/src/huffman/huff.h
+++ b/src/huffman/huff.h
@@ -28,8 +28,8 @@ class TInputStream
public:
TInputStream(void * pvInBuffer, size_t cbInBuffer);
- unsigned int Get1Bit();
- unsigned int Get8Bits();
+ bool Get1Bit(unsigned int & BitValue);
+ bool Get8Bits(unsigned int & ByteValue);
bool Peek7Bits(unsigned int & Value);
void SkipBits(unsigned int BitCount);
diff --git a/src/sparse/sparse.cpp b/src/sparse/sparse.cpp
index 6d1b621..6cf2df2 100644
--- a/src/sparse/sparse.cpp
+++ b/src/sparse/sparse.cpp
@@ -261,7 +261,12 @@ int DecompressSparse(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer,
// If highest bit, it means that that normal data follow
if(OneByte & 0x80)
{
+ // Check the length of one chunk. Check for overflows
cbChunkSize = (OneByte & 0x7F) + 1;
+ if((pbInBuffer + cbChunkSize) > pbInBufferEnd)
+ return 0;
+
+ // Copy the chunk. Make sure that the buffer won't overflow
cbChunkSize = (cbChunkSize < cbOutBuffer) ? cbChunkSize : cbOutBuffer;
memcpy(pbOutBuffer, pbInBuffer, cbChunkSize);
pbInBuffer += cbChunkSize;
diff --git a/test/StormTest.cpp b/test/StormTest.cpp
index 3276c59..1b13b89 100755
--- a/test/StormTest.cpp
+++ b/test/StormTest.cpp
@@ -3787,18 +3787,15 @@ static DWORD TestReplaceFile(LPCTSTR szMpqPlainName, LPCTSTR szFilePlainName, LP
static void Test_PlayingSpace()
{
- HANDLE hFile = NULL;
- HANDLE hMpq = NULL;
-
- if(SFileOpenArchive(_T("(4)Duskwood.w3m"), 0, 0, &hMpq))
- {
- if(SFileOpenFileEx(hMpq, "war3map.j", 0, &hFile))
- {
- SFileSetFileLocale(hFile, 1033);
- SFileCloseFile(hFile);
- }
- SFileCloseArchive(hMpq);
- }
+/*
+ i8 v0_tmp[] = {5, 34, -58, 65, 113, -118, 76, 11, 40, 32, 27, 20, 83, 15, 22, 46, 25, -24, -77, -88, -70, -118, -58, 56, 55, -94, -69, 43, -87, -1, -70, 0,}; // pvFileInfo
+ i8 * v0 = (i8 *)malloc(sizeof v0_tmp);
+ memcpy(v0, v0_tmp, sizeof v0_tmp);
+ i8 * v1 = v0; // pvFileInfo
+
+ enum _SFileInfoClass v2 = (enum _SFileInfoClass)(11); // InfoClass
+ i8 v3 = SFileFreeFileInfo(v1, v2); // $target
+*/
}
//-----------------------------------------------------------------------------
@@ -4002,6 +3999,21 @@ static const TEST_INFO1 TestList_MasterMirror[] =
static const TEST_INFO1 Test_OpenMpqs[] =
{
+
+ // PoC's by Gabe Sherman from FuturesLab
+ {_T("pocs/MPQ_2024_01_HeapOverrun.mpq"), NULL, "7008f95dcbc4e5d840830c176dec6969", 14},
+ {_T("pocs/MPQ_2024_02_StackOverflow.mpq"), NULL, "7093fcbcc9674b3e152e74e8e8a937bb", 4},
+ {_T("pocs/MPQ_2024_03_TooBigAlloc.mpq"), NULL, "--------------------------------", TFLG_WILL_FAIL},
+ {_T("pocs/MPQ_2024_04_HeapOverflow.mpq"), NULL, "--------------------------------", TFLG_WILL_FAIL},
+ {_T("pocs/MPQ_2024_05_HeapOverflow.mpq"), NULL, "0539ae020719654a0ea6e2627a8195f8", 14},
+ {_T("pocs/MPQ_2024_06_HeapOverflowReadFile.mpq"), NULL, "d41d8cd98f00b204e9800998ecf8427e", 1},
+ {_T("pocs/MPQ_2024_07_InvalidBitmapFooter.mpq"), NULL, "--------------------------------", TFLG_WILL_FAIL},
+ {_T("pocs/MPQ_2024_08_InvalidSectorSize.mpq"), NULL, "--------------------------------", TFLG_WILL_FAIL},
+ {_T("pocs/MPQ_2024_09_InvalidSectorSize.mpq"), NULL, "--------------------------------", TFLG_WILL_FAIL},
+ {_T("pocs/MPQ_2024_10_HuffDecompressError.mpq"), NULL, "--------------------------------", TFLG_WILL_FAIL},
+ {_T("pocs/MPQ_2024_10_SparseDecompressError.mpq"), NULL, "--------------------------------", TFLG_WILL_FAIL},
+ {_T("pocs/MPQ_2024_11_HiBlockTablePosInvalid.mpq"), NULL, "--------------------------------", TFLG_WILL_FAIL},
+
// Correct or damaged archives
{_T("MPQ_1997_v1_Diablo1_DIABDAT.MPQ"), NULL, "554b538541e42170ed41cb236483489e", 2910, &TwoFilesD1}, // Base MPQ from Diablo 1
{_T("MPQ_1997_v1_patch_rt_SC1B.mpq"), NULL, "43fe7d362955be68a708486e399576a7", 10}, // From Starcraft 1 BETA
@@ -4027,7 +4039,7 @@ static const TEST_INFO1 Test_OpenMpqs[] =
{_T("MPQ_2023_v1_BroodWarMap.scx"), NULL, "dd3afa3c2f5e562ce3ca91c0c605a71f", 3}, // Brood War map from StarCraft: Brood War 1.16
{_T("MPQ_2023_v1_Volcanis.scm"), NULL, "522c89ca96d6736427b01f7c80dd626f", 3}, // Map modified with unusual file compression: ZLIB+Huffman
{_T("MPQ_2023_v4_UTF8.s2ma"), NULL, "97b7a686650f3307d135e1d1b017a36a", 67}, // Map contaning files with Chinese names (UTF8-encoded)
- {_T("MPQ_2023_v1_GreenTD.w3x"), NULL, "477af4ddf11eead1412d7c87cb81b530", 2004}, // Corrupt sector checksum table in file #A0
+ {_T("MPQ_2023_v1_GreenTD.w3x"), NULL, "a8d91fc4e52d7c21ff7feb498c74781a", 2004}, // Corrupt sector checksum table in file #A0
{_T("MPQ_2023_v4_1F644C5A.SC2Replay"), NULL, "b225828ffbf5037553e6a1290187caab", 17}, // Corrupt patch info of the "(attributes)" file
{_T("<Chinese MPQ name>"), NULL, "67faeffd0c0aece205ac8b7282d8ad8e", 4697, &MpqUtf8}, // Chinese name of the MPQ
@@ -4211,7 +4223,7 @@ int _tmain(int argc, TCHAR * argv[])
#ifdef TEST_COMMAND_LINE
// Test-open MPQs from the command line. They must be plain name
// and must be placed in the Test-MPQs folder
- for(int i = 1; i < argc; i++)
+ for(int i = 2; i < argc; i++)
{
TestOpenArchive(argv[i], NULL, NULL, 0, &LfBliz);
}