diff --git a/distribution/changelog.txt b/distribution/changelog.txt index c20727e940..ead1212795 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -46,6 +46,7 @@ - Fix: [#6423] Polish characters now correctly drawn when using the sprite font. - Fix: [#6445] Guests' favourite ride improperly set when importing from RCT1 or AA. - Fix: [#6452] Scenario text cut off when switching between 32 and 64-bit builds. +- Fix: [#6460] Crash when reading corrupt object files. - Fix: Infinite loop when removing scenery elements with >127 base height. - Fix: Ghosting of transparent map elements when the viewport is moved in OpenGL mode. - Fix: Clear IME buffer after committing composed text. diff --git a/src/openrct2/object/ObjectRepository.cpp b/src/openrct2/object/ObjectRepository.cpp index 28b18610f3..33786a4f68 100644 --- a/src/openrct2/object/ObjectRepository.cpp +++ b/src/openrct2/object/ObjectRepository.cpp @@ -363,7 +363,7 @@ private: std::sort(_items.begin(), _items.end(), [](const ObjectRepositoryItem &a, const ObjectRepositoryItem &b) -> bool { - return strcmp(a.Name, b.Name) < 0; + return String::Compare(a.Name, b.Name) < 0; }); // Fix the IDs diff --git a/src/openrct2/rct12/SawyerChunkReader.cpp b/src/openrct2/rct12/SawyerChunkReader.cpp index db2919f34a..7c3ef586fd 100644 --- a/src/openrct2/rct12/SawyerChunkReader.cpp +++ b/src/openrct2/rct12/SawyerChunkReader.cpp @@ -17,13 +17,17 @@ #include "../core/Exception.hpp" #include "../core/IStream.hpp" #include "../core/Math.hpp" +#include "../core/Memory.hpp" #include "SawyerChunkReader.h" -#include "../util/sawyercoding.h" - // Allow chunks to be uncompressed to a maximum of 16 MiB constexpr size_t MAX_UNCOMPRESSED_CHUNK_SIZE = 16 * 1024 * 1024; +constexpr const char * EXCEPTION_MSG_CORRUPT_CHUNK_SIZE = "Corrupt chunk size."; +constexpr const char * EXCEPTION_MSG_DESTINATION_TOO_SMALL = "Chunk data larger than allocated destination capacity."; +constexpr const char * EXCEPTION_MSG_INVALID_CHUNK_ENCODING = "Invalid chunk encoding."; +constexpr const char * EXCEPTION_MSG_CORRUPT_RLE = "Corrupt RLE compression data."; + class SawyerChunkException : public IOException { public: @@ -67,7 +71,7 @@ std::shared_ptr SawyerChunkReader::ReadChunk() std::unique_ptr compressedData(new uint8[header.length]); if (_stream->TryRead(compressedData.get(), header.length) != header.length) { - throw SawyerChunkException("Corrupt chunk size."); + throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_CHUNK_SIZE); } // Allow 16MiB for chunk data @@ -78,7 +82,7 @@ std::shared_ptr SawyerChunkReader::ReadChunk() throw Exception("Unable to allocate buffer."); } - size_t uncompressedLength = sawyercoding_read_chunk_buffer(buffer, compressedData.get(), header, bufferSize); + size_t uncompressedLength = DecodeChunk(buffer, bufferSize, compressedData.get(), header); Guard::Assert(uncompressedLength != 0, "Encountered zero-sized chunk!"); buffer = Memory::Reallocate(buffer, uncompressedLength); if (buffer == nullptr) @@ -89,7 +93,7 @@ std::shared_ptr SawyerChunkReader::ReadChunk() return std::make_shared((SAWYER_ENCODING)header.encoding, buffer, uncompressedLength); } default: - throw SawyerChunkException("Invalid chunk encoding."); + throw SawyerChunkException(EXCEPTION_MSG_INVALID_CHUNK_ENCODING); } } catch (Exception) @@ -120,3 +124,126 @@ void SawyerChunkReader::ReadChunk(void * dst, size_t length) } } } + +size_t SawyerChunkReader::DecodeChunk(void * dst, size_t dstCapacity, const void * src, const sawyercoding_chunk_header &header) +{ + size_t resultLength; + switch (header.encoding) + { + case CHUNK_ENCODING_NONE: + if (header.length > dstCapacity) + { + throw SawyerChunkException(EXCEPTION_MSG_DESTINATION_TOO_SMALL); + } + Memory::Copy(dst, src, header.length); + resultLength = header.length; + break; + case CHUNK_ENCODING_RLE: + resultLength = DecodeChunkRLE(dst, dstCapacity, src, header.length); + break; + case CHUNK_ENCODING_RLECOMPRESSED: + { + auto immBufferLength = MAX_UNCOMPRESSED_CHUNK_SIZE; + auto immBuffer = std::make_unique(immBufferLength); + auto immLength = DecodeChunkRLE(immBuffer.get(), immBufferLength, src, header.length); + resultLength = DecodeChunkRepeat(dst, dstCapacity, immBuffer.get(), immLength); + break; + } + case CHUNK_ENCODING_ROTATE: + resultLength = DecodeChunkRotate(dst, dstCapacity, src, header.length); + break; + default: + throw SawyerChunkException(EXCEPTION_MSG_INVALID_CHUNK_ENCODING); + } + return resultLength; +} + +size_t SawyerChunkReader::DecodeChunkRLE(void * dst, size_t dstCapacity, const void * src, size_t srcLength) +{ + auto src8 = static_cast(src); + auto dst8 = static_cast(dst); + auto dstEnd = dst8 + dstCapacity; + for (size_t i = 0; i < srcLength; i++) + { + uint8 rleCodeByte = src8[i]; + if (rleCodeByte & 128) + { + i++; + size_t count = 257 - rleCodeByte; + + if (i >= srcLength) + { + throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_RLE); + } + if (dst8 + count > dstEnd) + { + throw SawyerChunkException(EXCEPTION_MSG_DESTINATION_TOO_SMALL); + } + + Memory::Set(dst8, src8[i], count); + dst8 += count; + } + else + { + if (i + 1 >= srcLength) + { + throw SawyerChunkException(EXCEPTION_MSG_CORRUPT_RLE); + } + if (dst8 + rleCodeByte + 1 > dstEnd) + { + throw SawyerChunkException(EXCEPTION_MSG_DESTINATION_TOO_SMALL); + } + + Memory::Copy(dst8, src8 + i + 1, rleCodeByte + 1); + dst8 += rleCodeByte + 1; + i += rleCodeByte + 1; + } + } + return (uintptr_t)dst8 - (uintptr_t)dst; +} + +size_t SawyerChunkReader::DecodeChunkRepeat(void * dst, size_t dstCapacity, const void * src, size_t srcLength) +{ + auto src8 = static_cast(src); + auto dst8 = static_cast(dst); + auto dstEnd = dst8 + dstCapacity; + for (size_t i = 0; i < srcLength; i++) + { + if (src8[i] == 0xFF) + { + *dst8++ = src8[++i]; + } + else + { + size_t count = (src8[i] & 7) + 1; + const uint8 * copySrc = dst8 + (sint32)(src8[i] >> 3) - 32; + + if (dst8 + count >= dstEnd || copySrc + count >= dstEnd) + { + throw SawyerChunkException(EXCEPTION_MSG_DESTINATION_TOO_SMALL); + } + + Memory::Copy(dst8, copySrc, count); + dst8 += count; + } + } + return (uintptr_t)dst8 - (uintptr_t)dst; +} + +size_t SawyerChunkReader::DecodeChunkRotate(void * dst, size_t dstCapacity, const void * src, size_t srcLength) +{ + if (srcLength > dstCapacity) + { + throw SawyerChunkException(EXCEPTION_MSG_DESTINATION_TOO_SMALL); + } + + auto src8 = static_cast(src); + auto dst8 = static_cast(dst); + uint8 code = 1; + for (size_t i = 0; i < srcLength; i++) + { + dst8[i] = ror8(src8[i], code); + code = (code + 2) % 8; + } + return srcLength; +} diff --git a/src/openrct2/rct12/SawyerChunkReader.h b/src/openrct2/rct12/SawyerChunkReader.h index 348f87f536..3c01a02130 100644 --- a/src/openrct2/rct12/SawyerChunkReader.h +++ b/src/openrct2/rct12/SawyerChunkReader.h @@ -20,6 +20,7 @@ #include #include "../common.h" +#include "../util/sawyercoding.h" #include "SawyerChunk.h" interface IStream; @@ -69,6 +70,12 @@ public: ReadChunk(&result, sizeof(result)); return result; } + +private: + static size_t DecodeChunk(void * dst, size_t dstCapacity, const void * src, const sawyercoding_chunk_header &header); + static size_t DecodeChunkRLE(void * dst, size_t dstCapacity, const void * src, size_t srcLength); + static size_t DecodeChunkRepeat(void * dst, size_t dstCapacity, const void * src, size_t srcLength); + static size_t DecodeChunkRotate(void * dst, size_t dstCapacity, const void * src, size_t srcLength); }; #endif