/* * Copyright 2014 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Reflection;using System.Collections.Generic; using System.IO; namespace Google.FlatBuffers { /// /// The Class of the Verifier Options /// public class Options { public const int DEFAULT_MAX_DEPTH = 64; public const int DEFAULT_MAX_TABLES = 1000000; private int max_depth = 0; private int max_tables = 0; private bool string_end_check = false; private bool alignment_check = false; public Options() { max_depth = DEFAULT_MAX_DEPTH; max_tables = DEFAULT_MAX_TABLES; string_end_check = true; alignment_check = true; } public Options(int maxDepth, int maxTables, bool stringEndCheck, bool alignmentCheck) { max_depth = maxDepth; max_tables = maxTables; string_end_check = stringEndCheck; alignment_check = alignmentCheck; } /// Maximum depth of nested tables allowed in a valid flatbuffer. public int maxDepth { get { return max_depth; } set { max_depth = value; } } /// Maximum number of tables allowed in a valid flatbuffer. public int maxTables { get { return max_tables; } set { max_tables = value; } } /// Check that string contains its null terminator public bool stringEndCheck { get { return string_end_check; } set { string_end_check = value; } } /// Check alignment of elements public bool alignmentCheck { get { return alignment_check; } set { alignment_check = value; } } } public struct checkElementStruct { public bool elementValid; public uint elementOffset; } public delegate bool VerifyTableAction(Verifier verifier, uint tablePos); public delegate bool VerifyUnionAction(Verifier verifier, byte typeId, uint tablePos); /// /// The Main Class of the FlatBuffer Verifier /// public class Verifier { private ByteBuffer verifier_buffer = null; private Options verifier_options = null; private int depth_cnt = 0; private int num_tables_cnt = 0; public const int SIZE_BYTE = 1; public const int SIZE_INT = 4; public const int SIZE_U_OFFSET = 4; public const int SIZE_S_OFFSET = 4; public const int SIZE_V_OFFSET = 2; public const int SIZE_PREFIX_LENGTH = FlatBufferConstants.SizePrefixLength; // default size = 4 public const int FLATBUFFERS_MAX_BUFFER_SIZE = System.Int32.MaxValue; // default size = 2147483647 public const int FILE_IDENTIFIER_LENGTH = FlatBufferConstants.FileIdentifierLength; // default size = 4 /// The Base Constructor of the Verifier object public Verifier() { // Verifier buffer verifier_buffer = null; // Verifier settings verifier_options = null; // Depth counter depth_cnt = 0; // Tables counter num_tables_cnt = 0; } /// The Constructor of the Verifier object with input parameters: ByteBuffer and/or Options /// Input flat byte buffer defined as ByteBuffer type /// Options object with settings for the coniguration the Verifier public Verifier(ByteBuffer buf, Options options = null) { verifier_buffer = buf; verifier_options = options ?? new Options(); depth_cnt = 0; num_tables_cnt = 0; } /// Bytes Buffer for Verify public ByteBuffer Buf { get { return verifier_buffer; } set { verifier_buffer = value; } } /// Options of the Verifier public Options options { get { return verifier_options; } set { verifier_options = value; } } /// Counter of tables depth in a tested flatbuffer public int depth { get { return depth_cnt; } set { depth_cnt = value; } } /// Counter of tables in a tested flatbuffer public int numTables { get { return num_tables_cnt; } set { num_tables_cnt = value; } } /// Method set maximum tables depth of valid structure /// Specify Value of the maximum depth of the structure public Verifier SetMaxDepth(int value) { verifier_options.maxDepth = value; return this; } /// Specify maximum number of tables in structure /// Specify Value of the maximum number of the tables in the structure public Verifier SetMaxTables(int value) { verifier_options.maxTables = value; return this; } /// Enable/disable buffer content alignment check /// Value of the State for buffer content alignment check (Enable = true) public Verifier SetAlignmentCheck(bool value) { verifier_options.alignmentCheck = value; return this; } /// Enable/disable checking of string termination '0' character /// Value of the option for string termination '0' character check (Enable = true) public Verifier SetStringCheck(bool value) { verifier_options.stringEndCheck = value; return this; } /// Check if there is identifier in buffer /// Input flat byte buffer defined as ByteBuffer type /// Start position of data in the Byte Buffer /// Identifier for the Byte Buffer /// Return True when the Byte Buffer Identifier is present private bool BufferHasIdentifier(ByteBuffer buf, uint startPos, string identifier) { if (identifier.Length != FILE_IDENTIFIER_LENGTH) { throw new ArgumentException("FlatBuffers: file identifier must be length" + Convert.ToString(FILE_IDENTIFIER_LENGTH)); } for (int i = 0; i < FILE_IDENTIFIER_LENGTH; i++) { if ((sbyte)identifier[i] != verifier_buffer.GetSbyte(Convert.ToInt32(SIZE_S_OFFSET + i + startPos))) { return false; } } return true; } /// Get UOffsetT from buffer at given position - it must be verified before read /// Input flat byte buffer defined as ByteBuffer type /// Position of data in the Byte Buffer /// Return the UOffset Value (Unsigned Integer type - 4 bytes) in pos private uint ReadUOffsetT(ByteBuffer buf, uint pos) { return buf.GetUint(Convert.ToInt32(pos)); } /// Get SOffsetT from buffer at given position - it must be verified before read /// Input flat byte buffer defined as ByteBuffer type /// Position of data in the Byte Buffer /// Return the SOffset Value (Signed Integer type - 4 bytes) in pos private int ReadSOffsetT(ByteBuffer buf, int pos) { return buf.GetInt(pos); } /// Get VOffsetT from buffer at given position - it must be verified before read /// Input flat byte buffer defined as ByteBuffer type /// Position of data in the Byte Buffer /// Return the VOffset Value (Short type - 2 bytes) in pos private short ReadVOffsetT(ByteBuffer buf, int pos) { return buf.GetShort(pos); } /// Get table data area relative offset from vtable. Result is relative to table start /// Fields which are deprecated are ignored by checking against the vtable's length. /// Position of data in the Byte Buffer /// offset of value in the Table /// Return the relative VOffset Value (Short type - 2 bytes) in calculated offset private short GetVRelOffset(int pos, short vtableOffset) { short VOffset = 0; // Used try/catch because pos typa as int 32bit try { // First, get vtable offset short vtable = Convert.ToInt16(pos - ReadSOffsetT(verifier_buffer, pos)); // Check that offset points to vtable area (is smaller than vtable size) if (vtableOffset < ReadVOffsetT(verifier_buffer, vtable)) { // Now, we can read offset value - TODO check this value against size of table data VOffset = ReadVOffsetT(verifier_buffer, vtable + vtableOffset); } else { VOffset = 0; } } catch (Exception e) { Console.WriteLine("Exception: {0}", e); return VOffset; } return VOffset; } /// Get table data area absolute offset from vtable. Result is the absolute buffer offset. /// The result value offset cannot be '0' (pointing to itself) so after validation this method returnes '0' /// value as a marker for missing optional entry /// Table Position value in the Byte Buffer /// offset value in the Table /// Return the absolute UOffset Value private uint GetVOffset(uint tablePos, short vtableOffset) { uint UOffset = 0; // First, get vtable relative offset short relPos = GetVRelOffset(Convert.ToInt32(tablePos), vtableOffset); if (relPos != 0) { // Calculate offset based on table postion UOffset = Convert.ToUInt32(tablePos + relPos); } else { UOffset = 0; } return UOffset; } /// Check flatbuffer complexity (tables depth, elements counter and so on) /// If complexity is too high function returns false as verification error private bool CheckComplexity() { return ((depth <= options.maxDepth) && (numTables <= options.maxTables)); } /// Check alignment of element. /// Return True when alignment of the element is correct private bool CheckAlignment(uint element, ulong align) { return (((element & (align - 1)) == 0) || (!options.alignmentCheck)); } /// Check if element is valid in buffer area. /// Value defines the offset/position to element /// Size of element /// Return True when Element is correct private bool CheckElement(uint pos, ulong elementSize) { return ((elementSize < Convert.ToUInt64(verifier_buffer.Length)) && (pos <= (Convert.ToUInt32(verifier_buffer.Length) - elementSize))); } /// Check if element is a valid scalar. /// Value defines the offset to scalar /// Size of element /// Return True when Scalar Element is correct private bool CheckScalar(uint pos, ulong elementSize) { return ((CheckAlignment(pos, elementSize)) && (CheckElement(pos, elementSize))); } /// Check offset. It is a scalar with size of UOffsetT. private bool CheckOffset(uint offset) { return (CheckScalar(offset, SIZE_U_OFFSET)); } private checkElementStruct CheckVectorOrString(uint pos, ulong elementSize) { var result = new checkElementStruct { elementValid = false, elementOffset = 0 }; uint vectorPos = pos; // Check we can read the vector/string size field (it is of uoffset size) if (!CheckScalar(vectorPos, SIZE_U_OFFSET)) { // result.elementValid = false; result.elementOffset = 0; return result; } // Check the whole array. If this is a string, the byte past the array // must be 0. uint size = ReadUOffsetT(verifier_buffer, vectorPos); ulong max_elements = (FLATBUFFERS_MAX_BUFFER_SIZE / elementSize); if (size >= max_elements) { // Protect against byte_size overflowing. // result.elementValid = false; result.elementOffset = 0; return result; } uint bytes_size = SIZE_U_OFFSET + (Convert.ToUInt32(elementSize) * size); uint buffer_end_pos = vectorPos + bytes_size; result.elementValid = CheckElement(vectorPos, bytes_size); result.elementOffset = buffer_end_pos; return (result); } /// Verify a string at given position. private bool CheckString(uint pos) { var result = CheckVectorOrString(pos, SIZE_BYTE); if (options.stringEndCheck) { result.elementValid = result.elementValid && CheckScalar(result.elementOffset, 1); // Must have terminator result.elementValid = result.elementValid && (verifier_buffer.GetSbyte(Convert.ToInt32(result.elementOffset)) == 0); // Terminating byte must be 0. } return result.elementValid; } /// Verify the vector of elements of given size private bool CheckVector(uint pos, ulong elementSize) { var result = CheckVectorOrString(pos, elementSize); return result.elementValid; } /// Verify table content using structure dependent generated function private bool CheckTable(uint tablePos, VerifyTableAction verifyAction) { return verifyAction(this, tablePos); } /// String check wrapper function to be used in vector of strings check private bool CheckStringFunc(Verifier verifier, uint pos) { return verifier.CheckString(pos); } /// Check vector of objects. Use generated object verification function private bool CheckVectorOfObjects(uint pos, VerifyTableAction verifyAction) { if (!CheckVector(pos, SIZE_U_OFFSET)) { return false; } uint size = ReadUOffsetT(verifier_buffer, pos); // Vector data starts just after vector size/length uint vecStart = pos + SIZE_U_OFFSET; uint vecOff = 0; // Iterate offsets and verify referenced objects for (uint i = 0; i < size; i++) { vecOff = vecStart + (i * SIZE_U_OFFSET); if (!CheckIndirectOffset(vecOff)) { return false; } uint objOffset = GetIndirectOffset(vecOff); if (!verifyAction(this, objOffset)) { return false; } } return true; } /// Check if the offset referenced by offsetPos is the valid offset pointing to buffer // offsetPos - offset to offset data private bool CheckIndirectOffset(uint pos) { // Check the input offset is valid if(!CheckScalar(pos, SIZE_U_OFFSET)) { return false; } // Get indirect offset uint offset = ReadUOffsetT(verifier_buffer, pos); // May not point to itself neither wrap around (buffers are max 2GB) if ((offset == 0) || (offset >= FLATBUFFERS_MAX_BUFFER_SIZE)) { return false; } // Must be inside the buffer return CheckElement(pos + offset, 1); } /// Check flatbuffer content using generated object verification function private bool CheckBufferFromStart(string identifier, uint startPos, VerifyTableAction verifyAction) { if ((identifier != null) && (identifier.Length == 0) && ((verifier_buffer.Length < (SIZE_U_OFFSET + FILE_IDENTIFIER_LENGTH)) || (!BufferHasIdentifier(verifier_buffer, startPos, identifier)))) { return false; } if(!CheckIndirectOffset(startPos)) { return false; } uint offset = GetIndirectOffset(startPos); return CheckTable(offset, verifyAction); // && GetComputedSize() } /// Get indirect offset. It is an offset referenced by offset Pos private uint GetIndirectOffset(uint pos) { // Get indirect offset referenced by offsetPos uint offset = pos + ReadUOffsetT(verifier_buffer, pos); return offset; } /// Verify beginning of table /// Position in the Table /// Return True when the verification of the beginning of the table is passed // (this method is used internally by generated verification functions) public bool VerifyTableStart(uint tablePos) { // Starting new table verification increases complexity of structure depth_cnt++; num_tables_cnt++; if (!CheckScalar(tablePos, SIZE_S_OFFSET)) { return false; } uint vtable = (uint)(tablePos - ReadSOffsetT(verifier_buffer, Convert.ToInt32(tablePos))); return ((CheckComplexity()) && (CheckScalar(vtable, SIZE_V_OFFSET)) && (CheckAlignment(Convert.ToUInt32(ReadVOffsetT(verifier_buffer, Convert.ToInt32(vtable))), SIZE_V_OFFSET)) && (CheckElement(vtable, Convert.ToUInt64(ReadVOffsetT(verifier_buffer, Convert.ToInt32(vtable)))))); } /// Verify end of table. In practice, this function does not check buffer but handles /// verification statistics update // (this method is used internally by generated verification functions) public bool VerifyTableEnd(uint tablePos) { depth--; return true; } /// Verifiy static/inlined data area field /// Position in the Table /// Offset to the static/inlined data element /// Size of the element /// Alignment bool value /// Required Value when the offset == 0 /// Return True when the verification of the static/inlined data element is passed // (this method is used internally by generated verification functions) public bool VerifyField(uint tablePos, short offsetId, ulong elementSize, ulong align, bool required) { uint offset = GetVOffset(tablePos, offsetId); if (offset != 0) { return ((CheckAlignment(offset, align)) && (CheckElement(offset, elementSize))); } return !required; // it is OK if field is not required } /// Verify string /// Position in the Table /// Offset to the String element /// Required Value when the offset == 0 /// Return True when the verification of the String is passed // (this method is used internally by generated verification functions) public bool VerifyString(uint tablePos, short vOffset, bool required) { var offset = GetVOffset(tablePos, vOffset); if (offset == 0) { return !required; } if (!CheckIndirectOffset(offset)) { return false; } var strOffset = GetIndirectOffset(offset); return CheckString(strOffset); } /// Verify vector of fixed size structures and scalars /// Position in the Table /// Offset to the Vector of Data /// Size of the element /// Required Value when the offset == 0 /// Return True when the verification of the Vector of Data passed // (this method is used internally by generated verification functions) public bool VerifyVectorOfData(uint tablePos, short vOffset, ulong elementSize, bool required) { var offset = GetVOffset(tablePos, vOffset); if (offset == 0) { return !required; } if (!CheckIndirectOffset(offset)) { return false; } var vecOffset = GetIndirectOffset(offset); return CheckVector(vecOffset, elementSize); } /// Verify array of strings /// Position in the Table /// Offset to the Vector of String /// Required Value when the offset == 0 /// Return True when the verification of the Vector of String passed // (this method is used internally by generated verification functions) public bool VerifyVectorOfStrings(uint tablePos, short offsetId, bool required) { var offset = GetVOffset(tablePos, offsetId); if (offset == 0) { return !required; } if (!CheckIndirectOffset(offset)) { return false; } var vecOffset = GetIndirectOffset(offset); return CheckVectorOfObjects(vecOffset, CheckStringFunc); } /// Verify vector of tables (objects). Tables are verified using generated verifyObjFunc /// Position in the Table /// Offset to the Vector of Table /// Method used to the verification Table /// Required Value when the offset == 0 /// Return True when the verification of the Vector of Table passed // (this method is used internally by generated verification functions) public bool VerifyVectorOfTables(uint tablePos, short offsetId, VerifyTableAction verifyAction, bool required) { var offset = GetVOffset(tablePos, offsetId); if (offset == 0) { return !required; } if (!CheckIndirectOffset(offset)) { return false; } var vecOffset = GetIndirectOffset(offset); return CheckVectorOfObjects(vecOffset, verifyAction); } /// Verify table object using generated verification function. /// Position in the Table /// Offset to the Table /// Method used to the verification Table /// Required Value when the offset == 0 /// Return True when the verification of the Table passed // (this method is used internally by generated verification functions) public bool VerifyTable(uint tablePos, short offsetId, VerifyTableAction verifyAction, bool required) { var offset = GetVOffset(tablePos, offsetId); if (offset == 0) { return !required; } if (!CheckIndirectOffset(offset)) { return false; } var tabOffset = GetIndirectOffset(offset); return CheckTable(tabOffset, verifyAction); } /// Verify nested buffer object. When verifyObjFunc is provided, it is used to verify object structure. /// Position in the Table /// Offset to the Table /// Method used to the verification Table /// Required Value when the offset == 0 // (this method is used internally by generated verification functions) public bool VerifyNestedBuffer(uint tablePos, short offsetId, VerifyTableAction verifyAction, bool required) { var offset = GetVOffset(tablePos, offsetId); if (offset == 0) { return !required; } uint vecOffset = GetIndirectOffset(offset); if (!CheckVector(vecOffset, SIZE_BYTE)) { return false; } if (verifyAction != null) { var vecLength = ReadUOffsetT(verifier_buffer, vecOffset); // Buffer begins after vector length var vecStart = vecOffset + SIZE_U_OFFSET; // Create and Copy nested buffer bytes from part of Verify Buffer var nestedByteBuffer = new ByteBuffer(verifier_buffer.ToArray(Convert.ToInt32(vecStart), Convert.ToInt32(vecLength))); var nestedVerifyier = new Verifier(nestedByteBuffer, options); // There is no internal identifier - use empty one if (!nestedVerifyier.CheckBufferFromStart("", 0, verifyAction)) { return false; } } return true; } /// Verifiy static/inlined data area at absolute offset /// Position of static/inlined data area in the Byte Buffer /// Size of the union data /// Alignment bool value /// Return True when the verification of the Union Data is passed // (this method is used internally by generated verification functions) public bool VerifyUnionData(uint pos, ulong elementSize, ulong align) { bool result = ((CheckAlignment(pos, align)) && (CheckElement(pos, elementSize))); return result; } /// Verify string referenced by absolute offset value /// Position of Union String in the Byte Buffer /// Return True when the verification of the Union String is passed // (this method is used internally by generated verification functions) public bool VerifyUnionString(uint pos) { bool result = CheckString(pos); return result; } /// Method verifies union object using generated verification function /// Position in the Table /// Offset in the Table /// Offset to Element /// Verification Method used for Union /// Required Value when the offset == 0 // (this method is used internally by generated verification functions) public bool VerifyUnion(uint tablePos, short typeIdVOffset, short valueVOffset, VerifyUnionAction verifyAction, bool required) { // Check the union type index var offset = GetVOffset(tablePos, typeIdVOffset); if (offset == 0) { return !required; } if (!((CheckAlignment(offset, SIZE_BYTE)) && (CheckElement(offset, SIZE_BYTE)))) { return false; } // Check union data offset = GetVOffset(tablePos, valueVOffset); // Take type id var typeId = verifier_buffer.Get(Convert.ToInt32(offset)); if (offset == 0) { // When value data is not present, allow union verification function to deal with illegal offset return verifyAction(this, typeId, Convert.ToUInt32(verifier_buffer.Length)); } if (!CheckIndirectOffset(offset)) { return false; } // Take value offset and validate union structure uint unionOffset = GetIndirectOffset(offset); return verifyAction(this, typeId, unionOffset); } /// Verify vector of unions (objects). Unions are verified using generated verifyObjFunc /// Position of the Table /// Offset in the Table (Union type id) /// Offset to vector of Data Stucture offset /// Verification Method used for Union /// Required Value when the offset == 0 /// Return True when the verification of the Vector of Unions passed // (this method is used internally by generated verification functions) public bool VerifyVectorOfUnion(uint tablePos, short typeOffsetId, short offsetId, VerifyUnionAction verifyAction, bool required) { // type id offset must be valid var offset = GetVOffset(tablePos, typeOffsetId); if (offset == 0) { return !required; } if (!CheckIndirectOffset(offset)) { return false; } // Get type id table absolute offset var typeIdVectorOffset = GetIndirectOffset(offset); // values offset must be valid offset = GetVOffset(tablePos, offsetId); if (!CheckIndirectOffset(offset)) { return false; } var valueVectorOffset = GetIndirectOffset(offset); // validate referenced vectors if(!CheckVector(typeIdVectorOffset, SIZE_BYTE) || !CheckVector(valueVectorOffset, SIZE_U_OFFSET)) { return false; } // Both vectors should have the same length var typeIdVectorLength = ReadUOffsetT(verifier_buffer, typeIdVectorOffset); var valueVectorLength = ReadUOffsetT(verifier_buffer, valueVectorOffset); if (typeIdVectorLength != valueVectorLength) { return false; } // Verify each union from vectors var typeIdStart = typeIdVectorOffset + SIZE_U_OFFSET; var valueStart = valueVectorOffset + SIZE_U_OFFSET; for (uint i = 0; i < typeIdVectorLength; i++) { // Get type id byte typeId = verifier_buffer.Get(Convert.ToInt32(typeIdStart + i * SIZE_U_OFFSET)); // get offset to vector item uint off = valueStart + i * SIZE_U_OFFSET; // Check the vector item has a proper offset if (!CheckIndirectOffset(off)) { return false; } uint valueOffset = GetIndirectOffset(off); // Verify object if (!verifyAction(this, typeId, valueOffset)) { return false; } } return true; } // Method verifies flatbuffer data using generated Table verification function. // The data buffer is already provided when creating [Verifier] object (see [NewVerifier]) // // - identifier - the expected identifier of buffer data. // When empty identifier is provided the identifier validation is skipped. // - sizePrefixed - this flag should be true when buffer is prefixed with content size // - verifyObjFunc - function to be used for verification. This function is generated by compiler and included in each table definition file with name "Verify" // // Example: // // /* Verify Monster table. Ignore buffer name and assume buffer does not contain data length prefix */ // isValid = verifier.verifyBuffer(bb, false, MonsterVerify) // // /* Verify Monster table. Buffer name is 'MONS' and contains data length prefix */ // isValid = verifier.verifyBuffer("MONS", true, MonsterVerify) /// Method verifies flatbuffer data using generated Table verification function /// /// The expected identifier of buffer data /// Flag should be true when buffer is prefixed with content size /// Function to be used for verification. This function is generated by compiler and included in each table definition file /// Return True when verification of FlatBuffer passed /// /// Example 1. Verify Monster table. Ignore buffer name and assume buffer does not contain data length prefix /// isValid = verifier.VerifyBuffer(bb, false, MonsterVerify) /// Example 2. Verify Monster table. Buffer name is 'MONS' and contains data length prefix /// isValid = verifier.VerifyBuffer("MONS", true, MonsterVerify) /// public bool VerifyBuffer(string identifier, bool sizePrefixed, VerifyTableAction verifyAction) { // Reset counters - starting verification from beginning depth = 0; numTables = 0; var start = (uint)(verifier_buffer.Position); if (sizePrefixed) { start = (uint)(verifier_buffer.Position) + SIZE_PREFIX_LENGTH; if(!CheckScalar((uint)(verifier_buffer.Position), SIZE_PREFIX_LENGTH)) { return false; } uint size = ReadUOffsetT(verifier_buffer, (uint)(verifier_buffer.Position)); if (size != ((uint)(verifier_buffer.Length) - start)) { return false; } } return CheckBufferFromStart(identifier, start, verifyAction); } } }