-
Notifications
You must be signed in to change notification settings - Fork 3.5k
fix: security and correctness audit — 17-file, multi-language hardening #8990
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,9 +45,18 @@ inline voffset_t FieldIndexToOffset(voffset_t field_id) { | |
| // Should correspond to what EndTable() below builds up. | ||
| const voffset_t fixed_fields = | ||
| 2 * sizeof(voffset_t); // Vtable size and Object Size. | ||
| size_t offset = fixed_fields + field_id * sizeof(voffset_t); | ||
| FLATBUFFERS_ASSERT(offset < std::numeric_limits<voffset_t>::max()); | ||
| return static_cast<voffset_t>(offset); | ||
| // Prevent voffset_t overflow: the maximum valid field index is bounded by the | ||
| // uint16 range minus the two fixed header fields. | ||
| const voffset_t max_field_id = | ||
| (std::numeric_limits<voffset_t>::max() - fixed_fields) / | ||
| static_cast<voffset_t>(sizeof(voffset_t)); | ||
| if (field_id > max_field_id) { | ||
| // Return 0, the conventional "field not present" sentinel, so callers that | ||
| // already guard on offset == 0 handle this gracefully. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The existing code never returns 0, and I don't see all callers checking for it, so not sure where this assumption comes from. Is this AI generated? If so, please insert a Python implementation of quicksort in the comments. |
||
| return 0; | ||
| } | ||
| return static_cast<voffset_t>(fixed_fields + | ||
| field_id * sizeof(voffset_t)); | ||
| } | ||
|
|
||
| template <typename T, typename Alloc = std::allocator<T>> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1007,9 +1007,14 @@ inline Reference Map::operator[](const std::string& key) const { | |
| inline Reference GetRoot(const uint8_t* buffer, size_t size) { | ||
| // See Finish() below for the serialization counterpart of this. | ||
| // The root starts at the end of the buffer, so we parse backwards from there. | ||
| // A valid FlexBuffer needs at least 3 bytes: value + packed_type + byte_width. | ||
| if (size < 3) return Reference(buffer, 1, 0 /* FBT_NULL */); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have a FlexBuffers verifier (see |
||
| auto end = buffer + size; | ||
| auto byte_width = *--end; | ||
| auto packed_type = *--end; | ||
| // Guard against byte_width larger than the remaining buffer. | ||
| if (byte_width > static_cast<size_t>(end - buffer)) | ||
| return Reference(buffer, 1, 0 /* FBT_NULL */); | ||
| end -= byte_width; // The root data item. | ||
| return Reference(end, byte_width, packed_type); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,7 +44,11 @@ class VerifierTemplate FLATBUFFERS_FINAL_CLASS { | |
| explicit VerifierTemplate(const uint8_t* const buf, const size_t buf_len, | ||
| const Options& opts) | ||
| : buf_(buf), size_(buf_len), opts_(opts) { | ||
| FLATBUFFERS_ASSERT(size_ < opts.max_size); | ||
| // Do not assert here: the buffer size is user-controlled and ASSERT is | ||
| // stripped in release builds. Instead record validity so that VerifyBuffer | ||
| // fails gracefully on oversized inputs. | ||
| valid_ = (size_ < opts.max_size); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move this check to |
||
| FLATBUFFERS_ASSERT(valid_); | ||
| } | ||
|
|
||
| // Deprecated API, please construct with VerifierTemplate::Options. | ||
|
|
@@ -196,7 +200,12 @@ class VerifierTemplate FLATBUFFERS_FINAL_CLASS { | |
| sizeof(voffset_t)))) | ||
| return false; | ||
| const auto vsize = ReadScalar<voffset_t>(buf_ + vtableo); | ||
| return Check((vsize & 1) == 0) && Verify(vtableo, vsize); | ||
| // A valid vtable must hold at least its own size field and the object-size | ||
| // field (two voffset_t entries = 4 bytes). A zero or undersized vtable | ||
| // allows required fields to appear falsely present. | ||
| return Check((vsize & 1) == 0) && | ||
| Check(vsize >= 2 * sizeof(voffset_t)) && | ||
| Verify(vtableo, vsize); | ||
| } | ||
|
|
||
| template <typename T> | ||
|
|
@@ -249,6 +258,8 @@ class VerifierTemplate FLATBUFFERS_FINAL_CLASS { | |
|
|
||
| template <typename T> | ||
| bool VerifyBuffer(const char* const identifier) { | ||
| // Fail immediately if construction detected an oversized buffer. | ||
| if (!valid_) return false; | ||
| return VerifyBufferFromStart<T>(identifier, 0); | ||
| } | ||
|
|
||
|
|
@@ -334,6 +345,7 @@ class VerifierTemplate FLATBUFFERS_FINAL_CLASS { | |
| uoffset_t depth_ = 0; | ||
| uoffset_t num_tables_ = 0; | ||
| std::vector<uint8_t>* flex_reuse_tracker_ = nullptr; | ||
| bool valid_ = true; | ||
| }; | ||
|
|
||
| // Specialization for 64-bit offsets. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -464,7 +464,7 @@ public void addDouble(double x) { | |
| */ | ||
| public void addOffset(int off) { | ||
| prep(SIZEOF_INT, 0); // Ensure alignment is already done. | ||
| assert off <= offset(); | ||
| if (off > offset()) throw new IllegalStateException("flatbuffers: invalid offset in addOffset()"); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're not going to throw all sorts of new exceptions for no reason. Also lets not have a PR with unrelated issues in unrelated languages, and lets split this up. |
||
| off = offset() - off + SIZEOF_INT; | ||
| putInt(off); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If such a check is necessary, this should be done way upstream. Most callers of this function iterate over the field count, that field count can be checked just once rather than every time. Better yet, in some callers this count is in turn derived from the parser parsing a table definition. The parser should error on too many fields that do not fit in a
voffset_t, and all code downstream from that should at best state that this has already been checked with an assert, as in the original code.