1 /* 2 * Copyright 2021 Google Inc. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #ifndef FLATBUFFERS_VERIFIER_H_ 18 #define FLATBUFFERS_VERIFIER_H_ 19 20 #include "flatbuffers/base.h" 21 #include "flatbuffers/vector.h" 22 23 namespace flatbuffers { 24 25 // Helper class to verify the integrity of a FlatBuffer 26 class Verifier FLATBUFFERS_FINAL_CLASS { 27 public: 28 struct Options { 29 // The maximum nesting of tables and vectors before we call it invalid. 30 uoffset_t max_depth = 64; 31 // The maximum number of tables we will verify before we call it invalid. 32 uoffset_t max_tables = 1000000; 33 // If true, verify all data is aligned. 34 bool check_alignment = true; 35 // If true, run verifier on nested flatbuffers 36 bool check_nested_flatbuffers = true; 37 // The maximum size of a buffer. 38 size_t max_size = FLATBUFFERS_MAX_BUFFER_SIZE; 39 // Use assertions to check for errors. 40 bool assert = false; 41 }; 42 Verifier(const uint8_t * const buf,const size_t buf_len,const Options & opts)43 explicit Verifier(const uint8_t *const buf, const size_t buf_len, 44 const Options &opts) 45 : buf_(buf), size_(buf_len), opts_(opts) { 46 FLATBUFFERS_ASSERT(size_ < opts.max_size); 47 } 48 49 // Deprecated API, please construct with Verifier::Options. 50 Verifier(const uint8_t *const buf, const size_t buf_len, 51 const uoffset_t max_depth = 64, const uoffset_t max_tables = 1000000, 52 const bool check_alignment = true) 53 : Verifier(buf, buf_len, [&] { 54 Options opts; 55 opts.max_depth = max_depth; 56 opts.max_tables = max_tables; 57 opts.check_alignment = check_alignment; 58 return opts; 59 }()) {} 60 61 // Central location where any verification failures register. Check(const bool ok)62 bool Check(const bool ok) const { 63 // clang-format off 64 #ifdef FLATBUFFERS_DEBUG_VERIFICATION_FAILURE 65 if (opts_.assert) { FLATBUFFERS_ASSERT(ok); } 66 #endif 67 #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE 68 if (!ok) 69 upper_bound_ = 0; 70 #endif 71 // clang-format on 72 return ok; 73 } 74 75 // Verify any range within the buffer. Verify(const size_t elem,const size_t elem_len)76 bool Verify(const size_t elem, const size_t elem_len) const { 77 // clang-format off 78 #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE 79 auto upper_bound = elem + elem_len; 80 if (upper_bound_ < upper_bound) 81 upper_bound_ = upper_bound; 82 #endif 83 // clang-format on 84 return Check(elem_len < size_ && elem <= size_ - elem_len); 85 } 86 VerifyAlignment(const size_t elem,const size_t align)87 bool VerifyAlignment(const size_t elem, const size_t align) const { 88 return Check((elem & (align - 1)) == 0 || !opts_.check_alignment); 89 } 90 91 // Verify a range indicated by sizeof(T). Verify(const size_t elem)92 template<typename T> bool Verify(const size_t elem) const { 93 return VerifyAlignment(elem, sizeof(T)) && Verify(elem, sizeof(T)); 94 } 95 VerifyFromPointer(const uint8_t * const p,const size_t len)96 bool VerifyFromPointer(const uint8_t *const p, const size_t len) { 97 return Verify(static_cast<size_t>(p - buf_), len); 98 } 99 100 // Verify relative to a known-good base pointer. VerifyFieldStruct(const uint8_t * const base,const voffset_t elem_off,const size_t elem_len,const size_t align)101 bool VerifyFieldStruct(const uint8_t *const base, const voffset_t elem_off, 102 const size_t elem_len, const size_t align) const { 103 const auto f = static_cast<size_t>(base - buf_) + elem_off; 104 return VerifyAlignment(f, align) && Verify(f, elem_len); 105 } 106 107 template<typename T> VerifyField(const uint8_t * const base,const voffset_t elem_off,const size_t align)108 bool VerifyField(const uint8_t *const base, const voffset_t elem_off, 109 const size_t align) const { 110 const auto f = static_cast<size_t>(base - buf_) + elem_off; 111 return VerifyAlignment(f, align) && Verify(f, sizeof(T)); 112 } 113 114 // Verify a pointer (may be NULL) of a table type. VerifyTable(const T * const table)115 template<typename T> bool VerifyTable(const T *const table) { 116 return !table || table->Verify(*this); 117 } 118 119 // Verify a pointer (may be NULL) of any vector type. 120 template<int &..., typename T, typename LenT> VerifyVector(const Vector<T,LenT> * const vec)121 bool VerifyVector(const Vector<T, LenT> *const vec) const { 122 return !vec || VerifyVectorOrString<LenT>( 123 reinterpret_cast<const uint8_t *>(vec), sizeof(T)); 124 } 125 126 // Verify a pointer (may be NULL) of a vector to struct. 127 template<int &..., typename T, typename LenT> VerifyVector(const Vector<const T *,LenT> * const vec)128 bool VerifyVector(const Vector<const T *, LenT> *const vec) const { 129 return VerifyVector(reinterpret_cast<const Vector<T, LenT> *>(vec)); 130 } 131 132 // Verify a pointer (may be NULL) to string. VerifyString(const String * const str)133 bool VerifyString(const String *const str) const { 134 size_t end; 135 return !str || (VerifyVectorOrString<uoffset_t>( 136 reinterpret_cast<const uint8_t *>(str), 1, &end) && 137 Verify(end, 1) && // Must have terminator 138 Check(buf_[end] == '\0')); // Terminating byte must be 0. 139 } 140 141 // Common code between vectors and strings. 142 template<typename LenT = uoffset_t> 143 bool VerifyVectorOrString(const uint8_t *const vec, const size_t elem_size, 144 size_t *const end = nullptr) const { 145 const auto vec_offset = static_cast<size_t>(vec - buf_); 146 // Check we can read the size field. 147 if (!Verify<LenT>(vec_offset)) return false; 148 // Check the whole array. If this is a string, the byte past the array must 149 // be 0. 150 const LenT size = ReadScalar<LenT>(vec); 151 const auto max_elems = opts_.max_size / elem_size; 152 if (!Check(size < max_elems)) 153 return false; // Protect against byte_size overflowing. 154 const auto byte_size = sizeof(LenT) + elem_size * size; 155 if (end) *end = vec_offset + byte_size; 156 return Verify(vec_offset, byte_size); 157 } 158 159 // Special case for string contents, after the above has been called. VerifyVectorOfStrings(const Vector<Offset<String>> * const vec)160 bool VerifyVectorOfStrings(const Vector<Offset<String>> *const vec) const { 161 if (vec) { 162 for (uoffset_t i = 0; i < vec->size(); i++) { 163 if (!VerifyString(vec->Get(i))) return false; 164 } 165 } 166 return true; 167 } 168 169 // Special case for table contents, after the above has been called. 170 template<typename T> VerifyVectorOfTables(const Vector<Offset<T>> * const vec)171 bool VerifyVectorOfTables(const Vector<Offset<T>> *const vec) { 172 if (vec) { 173 for (uoffset_t i = 0; i < vec->size(); i++) { 174 if (!vec->Get(i)->Verify(*this)) return false; 175 } 176 } 177 return true; 178 } 179 180 FLATBUFFERS_SUPPRESS_UBSAN("unsigned-integer-overflow") VerifyTableStart(const uint8_t * const table)181 bool VerifyTableStart(const uint8_t *const table) { 182 // Check the vtable offset. 183 const auto tableo = static_cast<size_t>(table - buf_); 184 if (!Verify<soffset_t>(tableo)) return false; 185 // This offset may be signed, but doing the subtraction unsigned always 186 // gives the result we want. 187 const auto vtableo = 188 tableo - static_cast<size_t>(ReadScalar<soffset_t>(table)); 189 // Check the vtable size field, then check vtable fits in its entirety. 190 if (!(VerifyComplexity() && Verify<voffset_t>(vtableo) && 191 VerifyAlignment(ReadScalar<voffset_t>(buf_ + vtableo), 192 sizeof(voffset_t)))) 193 return false; 194 const auto vsize = ReadScalar<voffset_t>(buf_ + vtableo); 195 return Check((vsize & 1) == 0) && Verify(vtableo, vsize); 196 } 197 198 template<typename T> VerifyBufferFromStart(const char * const identifier,const size_t start)199 bool VerifyBufferFromStart(const char *const identifier, const size_t start) { 200 // Buffers have to be of some size to be valid. The reason it is a runtime 201 // check instead of static_assert, is that nested flatbuffers go through 202 // this call and their size is determined at runtime. 203 if (!Check(size_ >= FLATBUFFERS_MIN_BUFFER_SIZE)) return false; 204 205 // If an identifier is provided, check that we have a buffer 206 if (identifier && !Check((size_ >= 2 * sizeof(flatbuffers::uoffset_t) && 207 BufferHasIdentifier(buf_ + start, identifier)))) { 208 return false; 209 } 210 211 // Call T::Verify, which must be in the generated code for this type. 212 const auto o = VerifyOffset<uoffset_t>(start); 213 return Check(o != 0) && 214 reinterpret_cast<const T *>(buf_ + start + o)->Verify(*this) 215 // clang-format off 216 #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE 217 && GetComputedSize() 218 #endif 219 ; 220 // clang-format on 221 } 222 223 template<typename T, int &..., typename SizeT> VerifyNestedFlatBuffer(const Vector<uint8_t,SizeT> * const buf,const char * const identifier)224 bool VerifyNestedFlatBuffer(const Vector<uint8_t, SizeT> *const buf, 225 const char *const identifier) { 226 // Caller opted out of this. 227 if (!opts_.check_nested_flatbuffers) return true; 228 229 // An empty buffer is OK as it indicates not present. 230 if (!buf) return true; 231 232 // If there is a nested buffer, it must be greater than the min size. 233 if (!Check(buf->size() >= FLATBUFFERS_MIN_BUFFER_SIZE)) return false; 234 235 Verifier nested_verifier(buf->data(), buf->size(), opts_); 236 return nested_verifier.VerifyBuffer<T>(identifier); 237 } 238 239 // Verify this whole buffer, starting with root type T. VerifyBuffer()240 template<typename T> bool VerifyBuffer() { return VerifyBuffer<T>(nullptr); } 241 VerifyBuffer(const char * const identifier)242 template<typename T> bool VerifyBuffer(const char *const identifier) { 243 return VerifyBufferFromStart<T>(identifier, 0); 244 } 245 246 template<typename T, typename SizeT = uoffset_t> VerifySizePrefixedBuffer(const char * const identifier)247 bool VerifySizePrefixedBuffer(const char *const identifier) { 248 return Verify<SizeT>(0U) && 249 // Ensure the prefixed size is within the bounds of the provided 250 // length. 251 Check(ReadScalar<SizeT>(buf_) + sizeof(SizeT) <= size_) && 252 VerifyBufferFromStart<T>(identifier, sizeof(SizeT)); 253 } 254 255 template<typename OffsetT = uoffset_t, typename SOffsetT = soffset_t> VerifyOffset(const size_t start)256 size_t VerifyOffset(const size_t start) const { 257 if (!Verify<OffsetT>(start)) return 0; 258 const auto o = ReadScalar<OffsetT>(buf_ + start); 259 // May not point to itself. 260 if (!Check(o != 0)) return 0; 261 // Can't wrap around larger than the max size. 262 if (!Check(static_cast<SOffsetT>(o) >= 0)) return 0; 263 // Must be inside the buffer to create a pointer from it (pointer outside 264 // buffer is UB). 265 if (!Verify(start + o, 1)) return 0; 266 return o; 267 } 268 269 template<typename OffsetT = uoffset_t> VerifyOffset(const uint8_t * const base,const voffset_t start)270 size_t VerifyOffset(const uint8_t *const base, const voffset_t start) const { 271 return VerifyOffset<OffsetT>(static_cast<size_t>(base - buf_) + start); 272 } 273 274 // Called at the start of a table to increase counters measuring data 275 // structure depth and amount, and possibly bails out with false if limits set 276 // by the constructor have been hit. Needs to be balanced with EndTable(). VerifyComplexity()277 bool VerifyComplexity() { 278 depth_++; 279 num_tables_++; 280 return Check(depth_ <= opts_.max_depth && num_tables_ <= opts_.max_tables); 281 } 282 283 // Called at the end of a table to pop the depth count. EndTable()284 bool EndTable() { 285 depth_--; 286 return true; 287 } 288 289 // Returns the message size in bytes GetComputedSize()290 size_t GetComputedSize() const { 291 // clang-format off 292 #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE 293 uintptr_t size = upper_bound_; 294 // Align the size to uoffset_t 295 size = (size - 1 + sizeof(uoffset_t)) & ~(sizeof(uoffset_t) - 1); 296 return (size > size_) ? 0 : size; 297 #else 298 // Must turn on FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE for this to work. 299 (void)upper_bound_; 300 FLATBUFFERS_ASSERT(false); 301 return 0; 302 #endif 303 // clang-format on 304 } 305 GetFlexReuseTracker()306 std::vector<uint8_t> *GetFlexReuseTracker() { return flex_reuse_tracker_; } 307 SetFlexReuseTracker(std::vector<uint8_t> * const rt)308 void SetFlexReuseTracker(std::vector<uint8_t> *const rt) { 309 flex_reuse_tracker_ = rt; 310 } 311 312 private: 313 const uint8_t *buf_; 314 const size_t size_; 315 const Options opts_; 316 317 mutable size_t upper_bound_ = 0; 318 319 uoffset_t depth_ = 0; 320 uoffset_t num_tables_ = 0; 321 std::vector<uint8_t> *flex_reuse_tracker_ = nullptr; 322 }; 323 324 // Specialization for 64-bit offsets. 325 template<> 326 inline size_t Verifier::VerifyOffset<uoffset64_t>(const size_t start) const { 327 return VerifyOffset<uoffset64_t, soffset64_t>(start); 328 } 329 330 } // namespace flatbuffers 331 332 #endif // FLATBUFFERS_VERIFIER_H_ 333