mirror of
https://github.com/aziascreations/PB-MessagePack.git
synced 2026-11-03 17:01:24 +01:00
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:
@@ -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. \
|
||||
Returns `#Null` on errors.
|
||||
|
||||
* Procedure.b MsgPackFree(*MsgPackData.MsgPackData, FreeDataBuffer.b = #False) \
|
||||
Frees a given internal structure `*MsgPackData` and its associated buffer if `FreeDataBuffer` is `#True`.
|
||||
* `MsgPackFree.b(*MsgPackData.MsgPackData, FreeDataBuffer.b)` \
|
||||
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.
|
||||
|
||||
* `MsgPackFinalize.i(*MsgPackData.MsgPackData)` \
|
||||
|
||||
@@ -95,7 +95,9 @@ Procedure.i MsgPackWrapMsgBuffer(*Buffer, BufferSize.i, GiveOwnership.b = #False
|
||||
ProcedureReturn *ReturnedValue
|
||||
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
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
|
||||
@@ -57,7 +57,7 @@ XIncludeFile "./_Primitives.pbi"
|
||||
; The caller is expected to write the extra header bytes and `DataSize` bytes of data,
|
||||
; and move the offset afterward.
|
||||
; 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 ErrorCode.MsgPack_ErrorCode
|
||||
|
||||
@@ -93,6 +93,13 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteSizedFieldHeader(*MsgPackData.MsgPackDa
|
||||
ProcedureReturn #MsgPack_Error_BinaryDataSizeIsTooBig
|
||||
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)
|
||||
If ErrorCode <> #MsgPack_Error_Success
|
||||
ProcedureReturn ErrorCode
|
||||
@@ -131,7 +138,7 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteSizedField(*MsgPackData.MsgPackData, Fo
|
||||
EndIf
|
||||
CompilerEndIf
|
||||
|
||||
ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, DataSize)
|
||||
ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, DataSize, 0, *Data)
|
||||
If ErrorCode <> #MsgPack_Error_Success
|
||||
ProcedureReturn ErrorCode
|
||||
EndIf
|
||||
|
||||
+26
-1
@@ -44,6 +44,18 @@ CompilerIf Not Defined(MsgPack_DisableNullChecks, #PB_Constant)
|
||||
#MsgPack_DisableNullChecks = #False
|
||||
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
|
||||
#MsgPack_Error_TooManyEntries = 70
|
||||
#MsgPack_Error_OddMapFieldCount = 71
|
||||
#MsgPack_Error_MaxDepthExceeded = 72
|
||||
|
||||
|
||||
#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.
|
||||
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.MsgPack_ErrorCode
|
||||
|
||||
@@ -284,6 +301,12 @@ Macro _MsgPackHasFlag(MsgPackWrapper, DataFlag)
|
||||
((MsgPackWrapper\DataFlags & DataFlag) <> $0000)
|
||||
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.
|
||||
Macro _MsgPackMoveOffsetAfterRead(MsgPackWrapper, Size)
|
||||
If _MsgPackHasFlag(MsgPackWrapper, #MsgPack_DataFlags_MoveOffsetAfterRead)
|
||||
@@ -321,7 +344,9 @@ EndProcedure
|
||||
CompilerIf #True
|
||||
Procedure _MsgPackDebug(Message.s)
|
||||
Debug Message
|
||||
OutputDebugString_(Message)
|
||||
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
|
||||
OutputDebugString_(Message)
|
||||
CompilerEndIf
|
||||
EndProcedure
|
||||
CompilerElse
|
||||
Macro _MsgPackDebug(Message) : : EndMacro
|
||||
|
||||
@@ -84,6 +84,13 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteExtField(*MsgPackData.MsgPackData, Form
|
||||
ProcedureReturn #MsgPack_Error_InvalidValue
|
||||
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)
|
||||
If ErrorCode <> #MsgPack_Error_Success
|
||||
ProcedureReturn ErrorCode
|
||||
@@ -92,7 +99,7 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteExtField(*MsgPackData.MsgPackData, Form
|
||||
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, FormatCode)
|
||||
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 1
|
||||
Else
|
||||
ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, DataSize, 1)
|
||||
ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, DataSize, 1, *Data)
|
||||
If ErrorCode <> #MsgPack_Error_Success
|
||||
ProcedureReturn ErrorCode
|
||||
EndIf
|
||||
|
||||
+68
-1
@@ -258,6 +258,13 @@ Procedure.MsgPack_ErrorCode _MsgPackGetFieldLayout(*MsgPackData.MsgPackData, *He
|
||||
ChildCount = ChildCount * 2
|
||||
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
|
||||
*ChildCount\q = ChildCount
|
||||
|
||||
@@ -278,8 +285,19 @@ Procedure.MsgPack_ErrorCode _MsgPackGetFieldLayout(*MsgPackData.MsgPackData, *He
|
||||
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.
|
||||
; 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.
|
||||
; Sets the structure's error code and returns it.
|
||||
Procedure.MsgPack_ErrorCode _MsgPackSkipFields(*MsgPackData.MsgPackData, FieldCount.q)
|
||||
@@ -287,6 +305,9 @@ Procedure.MsgPack_ErrorCode _MsgPackSkipFields(*MsgPackData.MsgPackData, FieldCo
|
||||
Protected HeaderSize.i
|
||||
Protected DataSize.q
|
||||
Protected ChildCount.q
|
||||
Protected Level.i = 0
|
||||
Protected Levels._MsgPackSkipLevels
|
||||
Protected FormatByte.a
|
||||
Protected ErrorCode.MsgPack_ErrorCode = #MsgPack_Error_Success
|
||||
|
||||
CompilerIf Not #MsgPack_DisableNullChecks
|
||||
@@ -298,14 +319,43 @@ Procedure.MsgPack_ErrorCode _MsgPackSkipFields(*MsgPackData.MsgPackData, FieldCo
|
||||
|
||||
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
|
||||
; 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)
|
||||
If ErrorCode <> #MsgPack_Error_Success
|
||||
Break
|
||||
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
|
||||
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.
|
||||
If FieldCount > *MsgPackData\BufferSize - *MsgPackData\BufferOffset
|
||||
@@ -442,6 +492,13 @@ Procedure.i MsgPackExamine(*MsgPackData.MsgPackData, ExaminationOptions.MsgPack_
|
||||
ProcedureReturn #Null
|
||||
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
|
||||
IsUnsafe = Bool(ExaminationOptions & #MsgPack_ExaminationOptions_Unsafe)
|
||||
|
||||
@@ -471,6 +528,7 @@ Procedure.i MsgPackExamine(*MsgPackData.MsgPackData, ExaminationOptions.MsgPack_
|
||||
*View\DataFlags = #_MsgPack_ViewDataFlags
|
||||
*View\IteratorEntryOffset = #MsgPack_InvalidSize
|
||||
*View\IteratorFieldsLeft = ChildCount
|
||||
*View\IteratorDepth = *MsgPackData\IteratorDepth + 1
|
||||
|
||||
If DataType = #MsgPack_DataTypes_Map
|
||||
*View\DataFlags = *View\DataFlags | #MsgPack_DataFlags_CanProvideKeys
|
||||
@@ -532,6 +590,13 @@ Procedure.b MsgPackNextEntry(*Iterator.MsgPackData)
|
||||
ProcedureReturn #False
|
||||
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
|
||||
EndProcedure
|
||||
|
||||
@@ -575,6 +640,8 @@ Procedure.q _MsgPackCountFields(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)
|
||||
; Temporary wrapper, so the regular procedures can walk the data.
|
||||
Source\Buffer = *Buffer
|
||||
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)
|
||||
If _MsgPackSkipFields(@Source, 1) <> #MsgPack_Error_Success
|
||||
@@ -607,7 +674,7 @@ Procedure.MsgPack_ErrorCode _MsgPackWriteContainer(*MsgPackData.MsgPackData, IsM
|
||||
CompilerEndIf
|
||||
|
||||
; 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 !")
|
||||
*MsgPackData\LastError = #MsgPack_Error_InvalidValue
|
||||
ProcedureReturn #MsgPack_Error_InvalidValue
|
||||
|
||||
@@ -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
|
||||
@@ -75,13 +75,18 @@ AssertEqualsQ(MsgPackGetFieldFullSize(*MsgPack), #MsgPack_InvalidSize, "Truncate
|
||||
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Truncated UInt16 error is set")
|
||||
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 = ""
|
||||
For i = 1 To 10000
|
||||
For i = 1 To 128
|
||||
Hex = Hex + "91"
|
||||
Next
|
||||
*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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user