From dfe7a0adee150bb00b611c48b50d3e928f27222f Mon Sep 17 00:00:00 2001 From: Mattes D Date: Thu, 30 Apr 2020 08:44:49 +0200 Subject: [PATCH] NBT: Dynamic list-max-count protection. (#4697) --- src/WorldStorage/FastNBT.cpp | 34 ++++++++++++++++++++++++++-------- src/WorldStorage/FastNBT.h | 4 ++++ src/WorldStorage/WSSAnvil.cpp | 7 ++++--- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/WorldStorage/FastNBT.cpp b/src/WorldStorage/FastNBT.cpp index 110bc68c8..d7901217d 100644 --- a/src/WorldStorage/FastNBT.cpp +++ b/src/WorldStorage/FastNBT.cpp @@ -10,13 +10,6 @@ -/** If a list being loaded has more than this number of items, it's considered corrupted. */ -static const int MAX_LIST_ITEMS = 10000; - - - - - // The number of NBT tags that are reserved when an NBT parsing is started. // You can override this by using a cmdline define #ifndef NBT_RESERVE_SIZE @@ -257,7 +250,8 @@ eNBTParseError cParsedNBT::ReadList(eTagType a_ChildrenType) NEEDBYTES(4, eNBTParseError::npListMissingLength); int Count = GetBEInt(m_Data + m_Pos); m_Pos += 4; - if ((Count < 0) || (Count > MAX_LIST_ITEMS)) + auto MinChildSize = GetMinTagSize(a_ChildrenType); + if ((Count < 0) || (Count > static_cast((m_Length - m_Pos) / MinChildSize))) { return eNBTParseError::npListInvalidLength; } @@ -445,6 +439,30 @@ int cParsedNBT::FindTagByPath(int a_Tag, const AString & a_Path) const +size_t cParsedNBT::GetMinTagSize(eTagType a_TagType) +{ + switch (a_TagType) + { + case TAG_End: return 1; + case TAG_Byte: return 1; + case TAG_Short: return 2; + case TAG_Int: return 4; + case TAG_Long: return 8; + case TAG_Float: return 4; + case TAG_Double: return 8; + case TAG_String: return 2; // 2 bytes for the string length + case TAG_ByteArray: return 4; // 4 bytes for the count + case TAG_List: return 5; // 1 byte list type + 4 bytes count + case TAG_Compound: return 1; // Single TAG_End byte + case TAG_IntArray: return 4; // 4 bytes for the count + } + UNREACHABLE("Unsupported nbt tag type"); +} + + + + + //////////////////////////////////////////////////////////////////////////////// // cFastNBTWriter: diff --git a/src/WorldStorage/FastNBT.h b/src/WorldStorage/FastNBT.h index 0185a49ec..1f11ec7f3 100644 --- a/src/WorldStorage/FastNBT.h +++ b/src/WorldStorage/FastNBT.h @@ -307,6 +307,10 @@ protected: eNBTParseError ReadCompound(void); // Reads the latest tag as a compound eNBTParseError ReadList(eTagType a_ChildrenType); // Reads the latest tag as a list of items of type a_ChildrenType eNBTParseError ReadTag(void); // Reads the latest tag, depending on its m_Type setting + + /** Returns the minimum size, in bytes, of the specified tag type. + Used for sanity-checking. */ + static size_t GetMinTagSize(eTagType a_TagType); } ; diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp index 96b8874a1..a5195196f 100755 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -326,7 +326,8 @@ bool cWSSAnvil::LoadChunkFromData(const cChunkCoords & a_Chunk, const AString & if (!NBT.IsValid()) { // NBT Parsing failed - ChunkLoadFailed(a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, "NBT parsing failed", a_Data); + auto msg = fmt::format("NBT parsing failed: {}, pos {}", NBT.GetErrorCode().message(), NBT.GetErrorPos()); + ChunkLoadFailed(a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, msg, a_Data); return false; } @@ -3520,8 +3521,8 @@ bool cWSSAnvil::cMCAFile::GetChunkData(const cChunkCoords & a_Chunk, AString & a m_ParentSchema.ChunkLoadFailed(a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, Printf("Unknown chunk compression: %d", CompressionType).c_str(), a_Data); return false; } - return true; - } + return true; +}