James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 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" |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 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: |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 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 | }; |
| 38 | |
| 39 | explicit Verifier(const uint8_t *const buf, const size_t buf_len, |
| 40 | const Options &opts) |
| 41 | : buf_(buf), size_(buf_len), opts_(opts) { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 42 | FLATBUFFERS_ASSERT(size_ < FLATBUFFERS_MAX_BUFFER_SIZE); |
| 43 | } |
| 44 | |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 45 | // Deprecated API, please construct with Verifier::Options. |
| 46 | Verifier(const uint8_t *const buf, const size_t buf_len, |
| 47 | const uoffset_t max_depth = 64, const uoffset_t max_tables = 1000000, |
| 48 | const bool check_alignment = true) |
| 49 | : Verifier(buf, buf_len, [&] { |
| 50 | Options opts; |
| 51 | opts.max_depth = max_depth; |
| 52 | opts.max_tables = max_tables; |
| 53 | opts.check_alignment = check_alignment; |
| 54 | return opts; |
| 55 | }()) {} |
| 56 | |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 57 | // Central location where any verification failures register. |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 58 | bool Check(const bool ok) const { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 59 | // clang-format off |
| 60 | #ifdef FLATBUFFERS_DEBUG_VERIFICATION_FAILURE |
| 61 | FLATBUFFERS_ASSERT(ok); |
| 62 | #endif |
| 63 | #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE |
| 64 | if (!ok) |
| 65 | upper_bound_ = 0; |
| 66 | #endif |
| 67 | // clang-format on |
| 68 | return ok; |
| 69 | } |
| 70 | |
| 71 | // Verify any range within the buffer. |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 72 | bool Verify(const size_t elem, const size_t elem_len) const { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 73 | // clang-format off |
| 74 | #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE |
| 75 | auto upper_bound = elem + elem_len; |
| 76 | if (upper_bound_ < upper_bound) |
| 77 | upper_bound_ = upper_bound; |
| 78 | #endif |
| 79 | // clang-format on |
| 80 | return Check(elem_len < size_ && elem <= size_ - elem_len); |
| 81 | } |
| 82 | |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 83 | bool VerifyAlignment(const size_t elem, const size_t align) const { |
| 84 | return Check((elem & (align - 1)) == 0 || !opts_.check_alignment); |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 85 | } |
| 86 | |
| 87 | // Verify a range indicated by sizeof(T). |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 88 | template<typename T> bool Verify(const size_t elem) const { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 89 | return VerifyAlignment(elem, sizeof(T)) && Verify(elem, sizeof(T)); |
| 90 | } |
| 91 | |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 92 | bool VerifyFromPointer(const uint8_t *const p, const size_t len) { |
| 93 | return Verify(static_cast<size_t>(p - buf_), len); |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 94 | } |
| 95 | |
| 96 | // Verify relative to a known-good base pointer. |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 97 | bool VerifyFieldStruct(const uint8_t *const base, const voffset_t elem_off, |
| 98 | const size_t elem_len, const size_t align) const { |
| 99 | const auto f = static_cast<size_t>(base - buf_) + elem_off; |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 100 | return VerifyAlignment(f, align) && Verify(f, elem_len); |
| 101 | } |
| 102 | |
| 103 | template<typename T> |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 104 | bool VerifyField(const uint8_t *const base, const voffset_t elem_off, |
| 105 | const size_t align) const { |
| 106 | const auto f = static_cast<size_t>(base - buf_) + elem_off; |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 107 | return VerifyAlignment(f, align) && Verify(f, sizeof(T)); |
| 108 | } |
| 109 | |
| 110 | // Verify a pointer (may be NULL) of a table type. |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 111 | template<typename T> bool VerifyTable(const T *const table) { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 112 | return !table || table->Verify(*this); |
| 113 | } |
| 114 | |
| 115 | // Verify a pointer (may be NULL) of any vector type. |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 116 | template<typename T> bool VerifyVector(const Vector<T> *const vec) const { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 117 | return !vec || VerifyVectorOrString(reinterpret_cast<const uint8_t *>(vec), |
| 118 | sizeof(T)); |
| 119 | } |
| 120 | |
| 121 | // Verify a pointer (may be NULL) of a vector to struct. |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 122 | template<typename T> |
| 123 | bool VerifyVector(const Vector<const T *> *const vec) const { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 124 | return VerifyVector(reinterpret_cast<const Vector<T> *>(vec)); |
| 125 | } |
| 126 | |
| 127 | // Verify a pointer (may be NULL) to string. |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 128 | bool VerifyString(const String *const str) const { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 129 | size_t end; |
| 130 | return !str || (VerifyVectorOrString(reinterpret_cast<const uint8_t *>(str), |
| 131 | 1, &end) && |
| 132 | Verify(end, 1) && // Must have terminator |
| 133 | Check(buf_[end] == '\0')); // Terminating byte must be 0. |
| 134 | } |
| 135 | |
| 136 | // Common code between vectors and strings. |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 137 | bool VerifyVectorOrString(const uint8_t *const vec, const size_t elem_size, |
| 138 | size_t *const end = nullptr) const { |
| 139 | const auto veco = static_cast<size_t>(vec - buf_); |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 140 | // Check we can read the size field. |
| 141 | if (!Verify<uoffset_t>(veco)) return false; |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 142 | // Check the whole array. If this is a string, the byte past the array must |
| 143 | // be 0. |
| 144 | const auto size = ReadScalar<uoffset_t>(vec); |
| 145 | const auto max_elems = FLATBUFFERS_MAX_BUFFER_SIZE / elem_size; |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 146 | if (!Check(size < max_elems)) |
| 147 | return false; // Protect against byte_size overflowing. |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 148 | const auto byte_size = sizeof(size) + elem_size * size; |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 149 | if (end) *end = veco + byte_size; |
| 150 | return Verify(veco, byte_size); |
| 151 | } |
| 152 | |
| 153 | // Special case for string contents, after the above has been called. |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 154 | bool VerifyVectorOfStrings(const Vector<Offset<String>> *const vec) const { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 155 | if (vec) { |
| 156 | for (uoffset_t i = 0; i < vec->size(); i++) { |
| 157 | if (!VerifyString(vec->Get(i))) return false; |
| 158 | } |
| 159 | } |
| 160 | return true; |
| 161 | } |
| 162 | |
| 163 | // Special case for table contents, after the above has been called. |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 164 | template<typename T> |
| 165 | bool VerifyVectorOfTables(const Vector<Offset<T>> *const vec) { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 166 | if (vec) { |
| 167 | for (uoffset_t i = 0; i < vec->size(); i++) { |
| 168 | if (!vec->Get(i)->Verify(*this)) return false; |
| 169 | } |
| 170 | } |
| 171 | return true; |
| 172 | } |
| 173 | |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 174 | __suppress_ubsan__("unsigned-integer-overflow") bool VerifyTableStart( |
| 175 | const uint8_t *const table) { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 176 | // Check the vtable offset. |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 177 | const auto tableo = static_cast<size_t>(table - buf_); |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 178 | if (!Verify<soffset_t>(tableo)) return false; |
| 179 | // This offset may be signed, but doing the subtraction unsigned always |
| 180 | // gives the result we want. |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 181 | const auto vtableo = |
| 182 | tableo - static_cast<size_t>(ReadScalar<soffset_t>(table)); |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 183 | // Check the vtable size field, then check vtable fits in its entirety. |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 184 | if (!(VerifyComplexity() && Verify<voffset_t>(vtableo) && |
| 185 | VerifyAlignment(ReadScalar<voffset_t>(buf_ + vtableo), |
| 186 | sizeof(voffset_t)))) |
| 187 | return false; |
| 188 | const auto vsize = ReadScalar<voffset_t>(buf_ + vtableo); |
| 189 | return Check((vsize & 1) == 0) && Verify(vtableo, vsize); |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 190 | } |
| 191 | |
| 192 | template<typename T> |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 193 | bool VerifyBufferFromStart(const char *const identifier, const size_t start) { |
| 194 | // Buffers have to be of some size to be valid. The reason it is a runtime |
| 195 | // check instead of static_assert, is that nested flatbuffers go through |
| 196 | // this call and their size is determined at runtime. |
| 197 | if (!Check(size_ >= FLATBUFFERS_MIN_BUFFER_SIZE)) return false; |
| 198 | |
| 199 | // If an identifier is provided, check that we have a buffer |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 200 | if (identifier && !Check((size_ >= 2 * sizeof(flatbuffers::uoffset_t) && |
| 201 | BufferHasIdentifier(buf_ + start, identifier)))) { |
| 202 | return false; |
| 203 | } |
| 204 | |
| 205 | // Call T::Verify, which must be in the generated code for this type. |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 206 | const auto o = VerifyOffset(start); |
| 207 | return Check(o != 0) && |
| 208 | reinterpret_cast<const T *>(buf_ + start + o)->Verify(*this) |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 209 | // clang-format off |
| 210 | #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE |
| 211 | && GetComputedSize() |
| 212 | #endif |
| 213 | ; |
| 214 | // clang-format on |
| 215 | } |
| 216 | |
| 217 | template<typename T> |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 218 | bool VerifyNestedFlatBuffer(const Vector<uint8_t> *const buf, |
| 219 | const char *const identifier) { |
| 220 | // Caller opted out of this. |
| 221 | if (!opts_.check_nested_flatbuffers) return true; |
| 222 | |
| 223 | // An empty buffer is OK as it indicates not present. |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 224 | if (!buf) return true; |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 225 | |
| 226 | // If there is a nested buffer, it must be greater than the min size. |
| 227 | if (!Check(buf->size() >= FLATBUFFERS_MIN_BUFFER_SIZE)) return false; |
| 228 | |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 229 | Verifier nested_verifier(buf->data(), buf->size()); |
| 230 | return nested_verifier.VerifyBuffer<T>(identifier); |
| 231 | } |
| 232 | |
| 233 | // Verify this whole buffer, starting with root type T. |
| 234 | template<typename T> bool VerifyBuffer() { return VerifyBuffer<T>(nullptr); } |
| 235 | |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 236 | template<typename T> bool VerifyBuffer(const char *const identifier) { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 237 | return VerifyBufferFromStart<T>(identifier, 0); |
| 238 | } |
| 239 | |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 240 | template<typename T> |
| 241 | bool VerifySizePrefixedBuffer(const char *const identifier) { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 242 | return Verify<uoffset_t>(0U) && |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 243 | Check(ReadScalar<uoffset_t>(buf_) == size_ - sizeof(uoffset_t)) && |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 244 | VerifyBufferFromStart<T>(identifier, sizeof(uoffset_t)); |
| 245 | } |
| 246 | |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 247 | uoffset_t VerifyOffset(const size_t start) const { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 248 | if (!Verify<uoffset_t>(start)) return 0; |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 249 | const auto o = ReadScalar<uoffset_t>(buf_ + start); |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 250 | // May not point to itself. |
| 251 | if (!Check(o != 0)) return 0; |
| 252 | // Can't wrap around / buffers are max 2GB. |
| 253 | if (!Check(static_cast<soffset_t>(o) >= 0)) return 0; |
| 254 | // Must be inside the buffer to create a pointer from it (pointer outside |
| 255 | // buffer is UB). |
| 256 | if (!Verify(start + o, 1)) return 0; |
| 257 | return o; |
| 258 | } |
| 259 | |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 260 | uoffset_t VerifyOffset(const uint8_t *const base, |
| 261 | const voffset_t start) const { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 262 | return VerifyOffset(static_cast<size_t>(base - buf_) + start); |
| 263 | } |
| 264 | |
| 265 | // Called at the start of a table to increase counters measuring data |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 266 | // structure depth and amount, and possibly bails out with false if limits set |
| 267 | // by the constructor have been hit. Needs to be balanced with EndTable(). |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 268 | bool VerifyComplexity() { |
| 269 | depth_++; |
| 270 | num_tables_++; |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 271 | return Check(depth_ <= opts_.max_depth && num_tables_ <= opts_.max_tables); |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 272 | } |
| 273 | |
| 274 | // Called at the end of a table to pop the depth count. |
| 275 | bool EndTable() { |
| 276 | depth_--; |
| 277 | return true; |
| 278 | } |
| 279 | |
| 280 | // Returns the message size in bytes |
| 281 | size_t GetComputedSize() const { |
| 282 | // clang-format off |
| 283 | #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE |
| 284 | uintptr_t size = upper_bound_; |
| 285 | // Align the size to uoffset_t |
| 286 | size = (size - 1 + sizeof(uoffset_t)) & ~(sizeof(uoffset_t) - 1); |
| 287 | return (size > size_) ? 0 : size; |
| 288 | #else |
| 289 | // Must turn on FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE for this to work. |
| 290 | (void)upper_bound_; |
| 291 | FLATBUFFERS_ASSERT(false); |
| 292 | return 0; |
| 293 | #endif |
| 294 | // clang-format on |
| 295 | } |
| 296 | |
| 297 | std::vector<uint8_t> *GetFlexReuseTracker() { return flex_reuse_tracker_; } |
| 298 | |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 299 | void SetFlexReuseTracker(std::vector<uint8_t> *const rt) { |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 300 | flex_reuse_tracker_ = rt; |
| 301 | } |
| 302 | |
| 303 | private: |
| 304 | const uint8_t *buf_; |
Austin Schuh | 2dd86a9 | 2022-09-14 21:19:23 -0700 | [diff] [blame^] | 305 | const size_t size_; |
| 306 | const Options opts_; |
| 307 | |
| 308 | mutable size_t upper_bound_ = 0; |
| 309 | |
| 310 | uoffset_t depth_ = 0; |
| 311 | uoffset_t num_tables_ = 0; |
| 312 | std::vector<uint8_t> *flex_reuse_tracker_ = nullptr; |
James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame] | 313 | }; |
| 314 | |
| 315 | } // namespace flatbuffers |
| 316 | |
| 317 | #endif // FLATBUFFERS_VERIFIER_H_ |