Fixed iterable elements DOS via depth, Fixed memory access bug, Improved default behaviour

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-11-02 23:41:53 +01:00
parent 30ad7aff2a
commit e19362a479
9 changed files with 355 additions and 11 deletions
+3 -2
View File
@@ -112,8 +112,9 @@ PureBasic include for creating/reading MessagePack data.
Wraps a given buffer `*Buffer` of a given size `InitialSize.i` into an internal structure. \ Wraps a given buffer `*Buffer` of a given size `InitialSize.i` into an internal structure. \
Returns `#Null` on errors. Returns `#Null` on errors.
* Procedure.b MsgPackFree(*MsgPackData.MsgPackData, FreeDataBuffer.b = #False) \ * `MsgPackFree.b(*MsgPackData.MsgPackData, FreeDataBuffer.b)` \
Frees a given internal structure `*MsgPackData` and its associated buffer if `FreeDataBuffer` is `#True`. Frees a given internal structure `*MsgPackData` and its associated buffer if `FreeDataBuffer` is `#True`. \
`FreeDataBuffer` is mandatory, use `#False` for buffers wrapped with `MsgPackWrapMsgBuffer` that the caller still owns. \
Returns `#True` if freed properly, `#False` otherwise. Returns `#True` if freed properly, `#False` otherwise.
* `MsgPackFinalize.i(*MsgPackData.MsgPackData)` \ * `MsgPackFinalize.i(*MsgPackData.MsgPackData)` \
+3 -1
View File
@@ -95,7 +95,9 @@ Procedure.i MsgPackWrapMsgBuffer(*Buffer, BufferSize.i, GiveOwnership.b = #False
ProcedureReturn *ReturnedValue ProcedureReturn *ReturnedValue
EndProcedure EndProcedure
Procedure.b MsgPackFree(*MsgPackData.MsgPackData, FreeDataBuffer.b = #False) ; Frees a structure, and its buffer too if `FreeDataBuffer` is `#True`.
; `FreeDataBuffer` has no default on purpose: forgetting it either leaks the buffer or frees one that's still in use.
Procedure.b MsgPackFree(*MsgPackData.MsgPackData, FreeDataBuffer.b)
If Not *MsgPackData If Not *MsgPackData
ProcedureReturn #False ProcedureReturn #False
EndIf EndIf
+9 -2
View File
@@ -57,7 +57,7 @@ XIncludeFile "./_Primitives.pbi"
; The caller is expected to write the extra header bytes and `DataSize` bytes of data, ; The caller is expected to write the extra header bytes and `DataSize` bytes of data,
; and move the offset afterward. ; and move the offset afterward.
; Sets the structure's error code and returns it. ; Sets the structure's error code and returns it.
Procedure.MsgPack_ErrorCode _MsgPackWriteSizedFieldHeader(*MsgPackData.MsgPackData, FormatCode.MsgPack_FormatCode, SizeFieldByteLength.a, DataSize.q, ExtraHeaderSize.a = 0) Procedure.MsgPack_ErrorCode _MsgPackWriteSizedFieldHeader(*MsgPackData.MsgPackData, FormatCode.MsgPack_FormatCode, SizeFieldByteLength.a, DataSize.q, ExtraHeaderSize.a = 0, *Data = #Null)
Protected MaxDataSize.q Protected MaxDataSize.q
Protected ErrorCode.MsgPack_ErrorCode Protected ErrorCode.MsgPack_ErrorCode
@@ -93,6 +93,13 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteSizedFieldHeader(*MsgPackData.MsgPackDa
ProcedureReturn #MsgPack_Error_BinaryDataSizeIsTooBig ProcedureReturn #MsgPack_Error_BinaryDataSizeIsTooBig
EndIf EndIf
; Growing the buffer would free data taken from it before it's copied.
If _MsgPackOverlapsBuffer(*MsgPackData, *Data, DataSize)
DebuggerError("Cannot write data located in the destination's own buffer !")
*MsgPackData\LastError = #MsgPack_Error_InvalidValue
ProcedureReturn #MsgPack_Error_InvalidValue
EndIf
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 1 + SizeFieldByteLength + ExtraHeaderSize + DataSize) ErrorCode = _MsgPackReserveWrite(*MsgPackData, 1 + SizeFieldByteLength + ExtraHeaderSize + DataSize)
If ErrorCode <> #MsgPack_Error_Success If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode ProcedureReturn ErrorCode
@@ -131,7 +138,7 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteSizedField(*MsgPackData.MsgPackData, Fo
EndIf EndIf
CompilerEndIf CompilerEndIf
ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, DataSize) ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, DataSize, 0, *Data)
If ErrorCode <> #MsgPack_Error_Success If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode ProcedureReturn ErrorCode
EndIf EndIf
+26 -1
View File
@@ -44,6 +44,18 @@ CompilerIf Not Defined(MsgPack_DisableNullChecks, #PB_Constant)
#MsgPack_DisableNullChecks = #False #MsgPack_DisableNullChecks = #False
CompilerEndIf CompilerEndIf
; Maximum nesting depth of arrays and maps, a top-level array or map being at depth 1.
; Deeper data is rejected as invalid with `#MsgPack_Error_MaxDepthExceeded`.
; Bounds the time spent on hostile data, nested views re-check their content at each level.
; Each depth level costs 8 bytes of stack per skip, hence the upper bound.
CompilerIf Not Defined(MsgPack_MaxDepth, #PB_Constant)
#MsgPack_MaxDepth = 128
CompilerEndIf
CompilerIf #MsgPack_MaxDepth < 1 Or #MsgPack_MaxDepth > 4096
CompilerError "#MsgPack_MaxDepth must be between 1 and 4096 !"
CompilerEndIf
; ------------------------------------------------------------------------------ ; ------------------------------------------------------------------------------
@@ -167,6 +179,7 @@ Enumeration _MsgPack_ErrorCodes
; 7x - Arrays & Maps ; 7x - Arrays & Maps
#MsgPack_Error_TooManyEntries = 70 #MsgPack_Error_TooManyEntries = 70
#MsgPack_Error_OddMapFieldCount = 71 #MsgPack_Error_OddMapFieldCount = 71
#MsgPack_Error_MaxDepthExceeded = 72
#MsgPack_Error_CannotGrowBufferDueToConfig = 100 #MsgPack_Error_CannotGrowBufferDueToConfig = 100
@@ -256,6 +269,10 @@ Structure MsgPackData
; Number of fields left to iterate on, map keys and values included, only used by views. ; Number of fields left to iterate on, map keys and values included, only used by views.
IteratorFieldsLeft.q IteratorFieldsLeft.q
; Number of arrays and maps containing the data, `0` for regular structures, `1` for views over a top-level one.
; Counted against `#MsgPack_MaxDepth`.
IteratorDepth.i
; LastError code ; LastError code
LastError.MsgPack_ErrorCode LastError.MsgPack_ErrorCode
@@ -284,6 +301,12 @@ Macro _MsgPackHasFlag(MsgPackWrapper, DataFlag)
((MsgPackWrapper\DataFlags & DataFlag) <> $0000) ((MsgPackWrapper\DataFlags & DataFlag) <> $0000)
EndMacro EndMacro
; Checks if the `Size` bytes at `Pointer` overlap the buffer of `MsgPackWrapper`.
; Writers refuse such data, growing the buffer would free it before it's copied.
Macro _MsgPackOverlapsBuffer(MsgPackWrapper, Pointer, Size)
(Pointer <> #Null And (Size) > 0 And Pointer < MsgPackWrapper\Buffer + MsgPackWrapper\BufferSize And Pointer + (Size) > MsgPackWrapper\Buffer)
EndMacro
; Moves the offset past a field of `Size` bytes that was fully read, if the flags allow it. ; Moves the offset past a field of `Size` bytes that was fully read, if the flags allow it.
Macro _MsgPackMoveOffsetAfterRead(MsgPackWrapper, Size) Macro _MsgPackMoveOffsetAfterRead(MsgPackWrapper, Size)
If _MsgPackHasFlag(MsgPackWrapper, #MsgPack_DataFlags_MoveOffsetAfterRead) If _MsgPackHasFlag(MsgPackWrapper, #MsgPack_DataFlags_MoveOffsetAfterRead)
@@ -321,7 +344,9 @@ EndProcedure
CompilerIf #True CompilerIf #True
Procedure _MsgPackDebug(Message.s) Procedure _MsgPackDebug(Message.s)
Debug Message Debug Message
OutputDebugString_(Message) CompilerIf #PB_Compiler_OS = #PB_OS_Windows
OutputDebugString_(Message)
CompilerEndIf
EndProcedure EndProcedure
CompilerElse CompilerElse
Macro _MsgPackDebug(Message) : : EndMacro Macro _MsgPackDebug(Message) : : EndMacro
+8 -1
View File
@@ -84,6 +84,13 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteExtField(*MsgPackData.MsgPackData, Form
ProcedureReturn #MsgPack_Error_InvalidValue ProcedureReturn #MsgPack_Error_InvalidValue
EndIf EndIf
; Growing the buffer would free data taken from it before it's copied.
If _MsgPackOverlapsBuffer(*MsgPackData, *Data, DataSize)
DebuggerError("Cannot write data located in the destination's own buffer !")
*MsgPackData\LastError = #MsgPack_Error_InvalidValue
ProcedureReturn #MsgPack_Error_InvalidValue
EndIf
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 2 + DataSize) ErrorCode = _MsgPackReserveWrite(*MsgPackData, 2 + DataSize)
If ErrorCode <> #MsgPack_Error_Success If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode ProcedureReturn ErrorCode
@@ -92,7 +99,7 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteExtField(*MsgPackData.MsgPackData, Form
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, FormatCode) PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, FormatCode)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 1 *MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 1
Else Else
ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, DataSize, 1) ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, DataSize, 1, *Data)
If ErrorCode <> #MsgPack_Error_Success If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode ProcedureReturn ErrorCode
EndIf EndIf
+68 -1
View File
@@ -258,6 +258,13 @@ Procedure.MsgPack_ErrorCode _MsgPackGetFieldLayout(*MsgPackData.MsgPackData, *He
ChildCount = ChildCount * 2 ChildCount = ChildCount * 2
EndIf EndIf
; Each contained field takes at least 1 byte, bogus counts are rejected right away.
If ChildCount > *MsgPackData\BufferSize - *MsgPackData\BufferOffset - ValueStartOffset
Debug("The array or map has more entries than the buffer can hold !")
*MsgPackData\LastError = #MsgPack_Error_BufferTooSmall
ProcedureReturn #MsgPack_Error_BufferTooSmall
EndIf
*HeaderSize\i = ValueStartOffset *HeaderSize\i = ValueStartOffset
*ChildCount\q = ChildCount *ChildCount\q = ChildCount
@@ -278,8 +285,19 @@ Procedure.MsgPack_ErrorCode _MsgPackGetFieldLayout(*MsgPackData.MsgPackData, *He
EndProcedure EndProcedure
; Checks if a format code byte is the one of an array or map, empty ones included.
Macro _MsgPackIsContainerFormatByte(Byte)
((Byte & $E0) = $80 Or (Byte >= $DC And Byte <= $DF))
EndMacro
; Fields left to skip at each nesting level, the top level being `0`.
Structure _MsgPackSkipLevels
FieldsLeft.q[#MsgPack_MaxDepth + 1]
EndStructure
; Moves the offset past the next `FieldCount` fields, the ones contained in arrays and maps included. ; Moves the offset past the next `FieldCount` fields, the ones contained in arrays and maps included.
; Done iteratively so deeply nested arrays and maps can't overflow the stack. ; Done iteratively so deeply nested arrays and maps can't overflow the stack.
; Arrays and maps nested deeper than `#MsgPack_MaxDepth`, counting the ones containing the structure, are rejected.
; The offset is left untouched on errors. ; The offset is left untouched on errors.
; Sets the structure's error code and returns it. ; Sets the structure's error code and returns it.
Procedure.MsgPack_ErrorCode _MsgPackSkipFields(*MsgPackData.MsgPackData, FieldCount.q) Procedure.MsgPack_ErrorCode _MsgPackSkipFields(*MsgPackData.MsgPackData, FieldCount.q)
@@ -287,6 +305,9 @@ Procedure.MsgPack_ErrorCode _MsgPackSkipFields(*MsgPackData.MsgPackData, FieldCo
Protected HeaderSize.i Protected HeaderSize.i
Protected DataSize.q Protected DataSize.q
Protected ChildCount.q Protected ChildCount.q
Protected Level.i = 0
Protected Levels._MsgPackSkipLevels
Protected FormatByte.a
Protected ErrorCode.MsgPack_ErrorCode = #MsgPack_Error_Success Protected ErrorCode.MsgPack_ErrorCode = #MsgPack_Error_Success
CompilerIf Not #MsgPack_DisableNullChecks CompilerIf Not #MsgPack_DisableNullChecks
@@ -298,14 +319,43 @@ Procedure.MsgPack_ErrorCode _MsgPackSkipFields(*MsgPackData.MsgPackData, FieldCo
StartOffset = *MsgPackData\BufferOffset StartOffset = *MsgPackData\BufferOffset
; Each field takes at least 1 byte, which also keeps `FieldCount` far from overflowing below.
If FieldCount > *MsgPackData\BufferSize - *MsgPackData\BufferOffset And Not _MsgPackIsAtTheEnd(*MsgPackData)
Debug("End of buffer reached, " + Str(FieldCount) + " fields can't fit !")
*MsgPackData\LastError = #MsgPack_Error_BufferTooSmall
ProcedureReturn #MsgPack_Error_BufferTooSmall
EndIf
; `FieldCount` is the total left to skip, all levels included.
Levels\FieldsLeft[0] = FieldCount
While FieldCount > 0 While FieldCount > 0
; Leaving the arrays and maps whose content was fully skipped.
While Levels\FieldsLeft[Level] = 0
Level = Level - 1
Wend
ErrorCode = _MsgPackGetFieldLayout(*MsgPackData, @HeaderSize, @DataSize, @ChildCount) ErrorCode = _MsgPackGetFieldLayout(*MsgPackData, @HeaderSize, @DataSize, @ChildCount)
If ErrorCode <> #MsgPack_Error_Success If ErrorCode <> #MsgPack_Error_Success
Break Break
EndIf EndIf
; Arrays and maps are at depth `Level + 1` from the structure's content.
FormatByte = PeekA(*MsgPackData\Buffer + *MsgPackData\BufferOffset)
If _MsgPackIsContainerFormatByte(FormatByte) And *MsgPackData\IteratorDepth + Level + 1 > #MsgPack_MaxDepth
Debug("Arrays and maps are nested deeper than " + Str(#MsgPack_MaxDepth) + " levels !")
*MsgPackData\LastError = #MsgPack_Error_MaxDepthExceeded
ErrorCode = #MsgPack_Error_MaxDepthExceeded
Break
EndIf
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + HeaderSize + DataSize *MsgPackData\BufferOffset = *MsgPackData\BufferOffset + HeaderSize + DataSize
FieldCount = FieldCount - 1 + ChildCount FieldCount = FieldCount - 1 + ChildCount
Levels\FieldsLeft[Level] = Levels\FieldsLeft[Level] - 1
If ChildCount > 0
Level = Level + 1
Levels\FieldsLeft[Level] = ChildCount
EndIf
; Each field takes at least 1 byte, which rejects bogus array and map sizes early. ; Each field takes at least 1 byte, which rejects bogus array and map sizes early.
If FieldCount > *MsgPackData\BufferSize - *MsgPackData\BufferOffset If FieldCount > *MsgPackData\BufferSize - *MsgPackData\BufferOffset
@@ -442,6 +492,13 @@ Procedure.i MsgPackExamine(*MsgPackData.MsgPackData, ExaminationOptions.MsgPack_
ProcedureReturn #Null ProcedureReturn #Null
EndIf EndIf
; Unsafe views aren't checked beforehand, nesting them is limited here.
If *MsgPackData\IteratorDepth >= #MsgPack_MaxDepth
Debug("Arrays and maps are nested deeper than " + Str(#MsgPack_MaxDepth) + " levels !")
*MsgPackData\LastError = #MsgPack_Error_MaxDepthExceeded
ProcedureReturn #Null
EndIf
StartOffset = *MsgPackData\BufferOffset StartOffset = *MsgPackData\BufferOffset
IsUnsafe = Bool(ExaminationOptions & #MsgPack_ExaminationOptions_Unsafe) IsUnsafe = Bool(ExaminationOptions & #MsgPack_ExaminationOptions_Unsafe)
@@ -471,6 +528,7 @@ Procedure.i MsgPackExamine(*MsgPackData.MsgPackData, ExaminationOptions.MsgPack_
*View\DataFlags = #_MsgPack_ViewDataFlags *View\DataFlags = #_MsgPack_ViewDataFlags
*View\IteratorEntryOffset = #MsgPack_InvalidSize *View\IteratorEntryOffset = #MsgPack_InvalidSize
*View\IteratorFieldsLeft = ChildCount *View\IteratorFieldsLeft = ChildCount
*View\IteratorDepth = *MsgPackData\IteratorDepth + 1
If DataType = #MsgPack_DataTypes_Map If DataType = #MsgPack_DataTypes_Map
*View\DataFlags = *View\DataFlags | #MsgPack_DataFlags_CanProvideKeys *View\DataFlags = *View\DataFlags | #MsgPack_DataFlags_CanProvideKeys
@@ -532,6 +590,13 @@ Procedure.b MsgPackNextEntry(*Iterator.MsgPackData)
ProcedureReturn #False ProcedureReturn #False
EndIf EndIf
; Unsafe views can announce more entries than their buffer holds.
If *Iterator\IteratorEntryOffset >= *Iterator\BufferSize
Debug("End of buffer reached, the remaining entries are missing !")
*Iterator\LastError = #MsgPack_Error_AtOrPastTheEndOfBuffer
ProcedureReturn #False
EndIf
ProcedureReturn #True ProcedureReturn #True
EndProcedure EndProcedure
@@ -575,6 +640,8 @@ Procedure.q _MsgPackCountFields(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)
; Temporary wrapper, so the regular procedures can walk the data. ; Temporary wrapper, so the regular procedures can walk the data.
Source\Buffer = *Buffer Source\Buffer = *Buffer
Source\BufferSize = BufferSize Source\BufferSize = BufferSize
; The fields will be in the written array or map, so the result stays within `#MsgPack_MaxDepth`.
Source\IteratorDepth = 1
While Not _MsgPackIsAtTheEnd(Source) While Not _MsgPackIsAtTheEnd(Source)
If _MsgPackSkipFields(@Source, 1) <> #MsgPack_Error_Success If _MsgPackSkipFields(@Source, 1) <> #MsgPack_Error_Success
@@ -607,7 +674,7 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteContainer(*MsgPackData.MsgPackData, IsM
CompilerEndIf CompilerEndIf
; The content's address would be invalidated when growing. ; The content's address would be invalidated when growing.
If *Buffer And *Buffer >= *MsgPackData\Buffer And *Buffer < *MsgPackData\Buffer + *MsgPackData\BufferSize If _MsgPackOverlapsBuffer(*MsgPackData, *Buffer, BufferSize)
DebuggerError("Cannot write content located in the destination's own buffer !") DebuggerError("Cannot write content located in the destination's own buffer !")
*MsgPackData\LastError = #MsgPack_Error_InvalidValue *MsgPackData\LastError = #MsgPack_Error_InvalidValue
ProcedureReturn #MsgPack_Error_InvalidValue ProcedureReturn #MsgPack_Error_InvalidValue
+153
View File
@@ -0,0 +1,153 @@
EnableExplicit
; Must be defined before the include, it replaces the default of 128.
#MsgPack_MaxDepth = 3
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; Setup
Define *MsgPack.MsgPackData
Define *Content.MsgPackData
Define *Output.MsgPackData
Define *View1.MsgPackData
Define *View2.MsgPackData
Define *View3.MsgPackData
Define *View4.MsgPackData
Procedure TestDepth(Hex.s, ExpectedSize.q, TestName.s)
Protected *MsgPack.MsgPackData = WrapHex(Hex)
AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), ExpectedSize, TestName + " (Size)")
If ExpectedSize = #MsgPack_InvalidSize
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_MaxDepthExceeded, TestName + " (Error)")
AssertEquals(MsgPackSkip(*MsgPack), #MsgPack_Error_MaxDepthExceeded, TestName + " (Skip)")
AssertEquals(*MsgPack\BufferOffset, 0, TestName + " (Skip leaves the offset)")
AssertIsTrue(Bool(MsgPackExamine(*MsgPack) = #Null), TestName + " (Examine)")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_MaxDepthExceeded, TestName + " (Examine error)")
AssertEquals(*MsgPack\BufferOffset, 0, TestName + " (Examine leaves the offset)")
Else
AssertEquals(MsgPackSkip(*MsgPack), #MsgPack_Error_Success, TestName + " (Skip)")
AssertEquals(*MsgPack\BufferOffset, ExpectedSize, TestName + " (Skip offset)")
EndIf
FreeWrappedHex(*MsgPack)
EndProcedure
; Boundaries, a top-level array or map being at depth 1
TestDepth("919191C0", 4, "Arrays nested 3 levels deep")
TestDepth("91919191C0", #MsgPack_InvalidSize, "Arrays nested 4 levels deep")
TestDepth("919190", 3, "Empty array at depth 3")
TestDepth("91919190", #MsgPack_InvalidSize, "Empty array at depth 4")
TestDepth("91919180", #MsgPack_InvalidSize, "Empty map at depth 4")
TestDepth("DC0001DC0001DC0001C0", 10, "Array16 nested 3 levels deep")
TestDepth("DD00000001DD00000001DD00000001DD00000001C0", #MsgPack_InvalidSize, "Array32 nested 4 levels deep")
; Maps, as keys and as values
TestDepth("818180C0C0", 5, "Maps nested 3 levels deep as keys")
TestDepth("81818180C0C0C0", #MsgPack_InvalidSize, "Maps nested 4 levels deep as keys")
TestDepth("81C081C081C0C0", 7, "Maps nested 3 levels deep as values")
TestDepth("81C081C081C081C0C0", #MsgPack_InvalidSize, "Maps nested 4 levels deep as values")
; The depth goes back down between siblings
TestDepth("9291C091C0", 5, "Siblings at depth 2")
TestDepth("929191C09191C0", 7, "Siblings reaching depth 3")
TestDepth("82C09191C0C0C0", 7, "Map value reaching depth 3")
TestDepth("92C0919191C0", #MsgPack_InvalidSize, "Second sibling reaching depth 4")
TestDepth("939191C0C0919191C0", #MsgPack_InvalidSize, "Third sibling reaching depth 4")
; Skipping several fields, each one starting back at depth 1
*MsgPack = WrapHex("919191C0919191C0")
AssertEquals(MsgPackSkip(*MsgPack, 2), #MsgPack_Error_Success, "Skipped 2 fields 3 levels deep")
AssertEquals(*MsgPack\BufferOffset, 8, "Offset after the 2 fields")
FreeWrappedHex(*MsgPack)
; Checked views: each level is reachable, the content was checked by the top-level one
*MsgPack = WrapHex("919191C0")
*View1 = MsgPackExamine(*MsgPack)
AssertIsTrue(Bool(*View1 <> #Null), "Examined depth 1")
AssertIsTrue(MsgPackNextEntry(*View1), "Depth 1 entry")
*View2 = MsgPackExamine(*View1)
AssertIsTrue(Bool(*View2 <> #Null), "Examined depth 2")
AssertIsTrue(MsgPackNextEntry(*View2), "Depth 2 entry")
*View3 = MsgPackExamine(*View2)
AssertIsTrue(Bool(*View3 <> #Null), "Examined depth 3")
AssertIsTrue(MsgPackNextEntry(*View3), "Depth 3 entry")
AssertEquals(MsgPackReadNull(*View3), #MsgPack_Error_Success, "Read the value at depth 3")
AssertIsFalse(MsgPackNextEntry(*View3), "End of depth 3")
MsgPackFinish(*View3)
AssertIsFalse(MsgPackNextEntry(*View2), "End of depth 2")
MsgPackFinish(*View2)
AssertIsFalse(MsgPackNextEntry(*View1), "End of depth 1")
MsgPackFinish(*View1)
FreeWrappedHex(*MsgPack)
; Unsafe views aren't checked beforehand, their own nesting is limited
*MsgPack = WrapHex("91919191C0")
*View1 = MsgPackExamine(*MsgPack, #MsgPack_ExaminationOptions_Unsafe)
AssertIsTrue(Bool(*View1 <> #Null), "Unsafe examined depth 1")
AssertIsTrue(MsgPackNextEntry(*View1), "Unsafe depth 1 entry")
*View2 = MsgPackExamine(*View1, #MsgPack_ExaminationOptions_Unsafe)
AssertIsTrue(Bool(*View2 <> #Null), "Unsafe examined depth 2")
AssertIsTrue(MsgPackNextEntry(*View2), "Unsafe depth 2 entry")
*View3 = MsgPackExamine(*View2, #MsgPack_ExaminationOptions_Unsafe)
AssertIsTrue(Bool(*View3 <> #Null), "Unsafe examined depth 3")
AssertIsTrue(MsgPackNextEntry(*View3), "Unsafe depth 3 entry")
*View4 = MsgPackExamine(*View3, #MsgPack_ExaminationOptions_Unsafe)
AssertIsTrue(Bool(*View4 = #Null), "Unsafe depth 4 is rejected")
AssertEquals(MsgPackLastError(*View3), #MsgPack_Error_MaxDepthExceeded, "Unsafe depth 4 error is set")
*View4 = MsgPackExamine(*View3)
AssertIsTrue(Bool(*View4 = #Null), "Checked depth 4 is rejected too")
AssertEquals(MsgPackLastError(*View3), #MsgPack_Error_MaxDepthExceeded, "Checked depth 4 error is set")
AssertIsFalse(MsgPackNextEntry(*View3), "The depth 4 array can't be skipped")
AssertEquals(MsgPackLastError(*View3), #MsgPack_Error_MaxDepthExceeded, "Skip error is set")
MsgPackFinish(*View3)
AssertIsFalse(MsgPackNextEntry(*View2), "Depth 2 can't skip its entry either")
MsgPackFinish(*View2)
MsgPackFinish(*View1)
FreeWrappedHex(*MsgPack)
; Writers can't produce data deeper than the limit
*Output = MsgPackCreate(16)
*Content = WrapHex("9191C0")
AssertEquals(MsgPackWriteArray(*Output, *Content\Buffer, *Content\BufferSize), #MsgPack_Error_Success, "Array of content 2 levels deep")
AssertEquals(*Output\BufferOffset, 4, "Array written")
FreeWrappedHex(*Content)
*Content = WrapHex("919191C0")
AssertEquals(MsgPackWriteArray(*Output, *Content\Buffer, *Content\BufferSize), #MsgPack_Error_MaxDepthExceeded, "Array of content 3 levels deep")
AssertEquals(MsgPackLastError(*Output), #MsgPack_Error_MaxDepthExceeded, "Writer error is set")
AssertEquals(MsgPackWriteMap(*Output, *Content\Buffer, *Content\BufferSize), #MsgPack_Error_MaxDepthExceeded, "Map of content 3 levels deep")
AssertEquals(*Output\BufferOffset, 4, "Nothing was written")
FreeWrappedHex(*Content)
*Content = WrapHex("C09191C0")
AssertEquals(MsgPackWriteMap(*Output, *Content\Buffer, *Content\BufferSize), #MsgPack_Error_Success, "Map with a value 2 levels deep")
AssertEquals(*Output\BufferOffset, 9, "Map written")
FreeWrappedHex(*Content)
; The written data is at the limit, and can be read back
*Output\BufferOffset = 0
AssertEqualsQ(MsgPackGetFieldFullSize(*Output), 4, "Written array is readable")
*Output\BufferOffset = 4
AssertEqualsQ(MsgPackGetFieldFullSize(*Output), 5, "Written map is readable")
MsgPackFree(*Output, #True)
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
@@ -0,0 +1,77 @@
EnableExplicit
; Biggest supported value, deep nesting must still be handled iteratively.
#MsgPack_MaxDepth = 4096
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; Setup
Define *MsgPack.MsgPackData
Define *Buffer
Define i.i
Define Depth.i
Define *View.MsgPackData
Dim Views.i(4096)
Procedure.i WrapNested(Depth.i, Size.i)
Protected *Buffer = AllocateMemory(Size)
FillMemory(*Buffer, Depth, $91)
FillMemory(*Buffer + Depth, Size - Depth, $C0)
ProcedureReturn MsgPackWrapMsgBuffer(*Buffer, Size)
EndProcedure
; At the limit
*MsgPack = WrapNested(4096, 4097)
AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), 4097, "Arrays nested 4096 levels deep")
AssertEquals(MsgPackSkip(*MsgPack), #MsgPack_Error_Success, "Skipped arrays nested 4096 levels deep")
; Every level examined, with checked views
*MsgPack\BufferOffset = 0
Views(0) = *MsgPack
For i = 1 To 4096
Views(i) = MsgPackExamine(Views(i - 1))
If Not Views(i)
Break
EndIf
If Not MsgPackNextEntry(Views(i))
MsgPackFinish(Views(i))
Break
EndIf
Next
Depth = i - 1
AssertEquals(Depth, 4096, "Examined every level")
If Depth = 4096
AssertEquals(MsgPackReadNull(Views(4096)), #MsgPack_Error_Success, "Read the value at depth 4096")
EndIf
For i = Depth To 1 Step -1
*View = Views(i)
MsgPackFinish(*View)
Next
FreeWrappedHex(*MsgPack)
; Past the limit
*MsgPack = WrapNested(4097, 4098)
AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), #MsgPack_InvalidSize, "Arrays nested 4097 levels deep")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_MaxDepthExceeded, "Max depth error is set")
FreeWrappedHex(*MsgPack)
; Far past it, rejected without walking the rest
*MsgPack = WrapNested(1000000, 1000001)
AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), #MsgPack_InvalidSize, "Arrays nested 1M levels deep")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_MaxDepthExceeded, "Max depth error is set for 1M levels")
FreeWrappedHex(*MsgPack)
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
+8 -3
View File
@@ -75,13 +75,18 @@ AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), #MsgPack_InvalidSize, "Truncate
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Truncated UInt16 error is set") AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Truncated UInt16 error is set")
FreeWrappedHex(*MsgPack) FreeWrappedHex(*MsgPack)
; Deep nesting doesn't overflow the stack ; Deep nesting, up to `#MsgPack_MaxDepth` (128 by default), see "TestDepthLimit*.pb" for the rest
Hex = "" Hex = ""
For i = 1 To 10000 For i = 1 To 128
Hex = Hex + "91" Hex = Hex + "91"
Next Next
*MsgPack = WrapHex(Hex + "01") *MsgPack = WrapHex(Hex + "01")
AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), 10001, "Deeply nested arrays") AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), 129, "Arrays nested 128 levels deep")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("91" + Hex + "01")
AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), #MsgPack_InvalidSize, "Arrays nested 129 levels deep are rejected")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_MaxDepthExceeded, "Max depth error is set")
FreeWrappedHex(*MsgPack) FreeWrappedHex(*MsgPack)