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