Repository navigation
Conversation
|
Hi, is there an update for this? Thank you, |
|
@jortles looks like there are failing tests, can you fix? |
4195606 to
c9d448d
Compare
5fc75e2 to
c7640e2
Compare
|
Hi @karenwuz, sorry for the delay. The CI failure was the I've rebased the branch on current
Please let me know if there's anything else needed. |
|
I'll apply the tag and retry. |
|
Did you reply to my review comment above or update your commit? I don't see any diffs compared to before and I don't see a response to my review comment. |
|
Sorry @tonyliaoss β I think your review comment may have been lost when the branch was force-pushed. I'm not seeing it in the conversation thread or the files tab. Could you re-post it? Happy to address whatever was needed. |
tonyliaoss
left a comment
There was a problem hiding this comment.
Ugh, I didn't hit "submit review" :(
| absl::flat_hash_set<int> swapped_oneof; | ||
|
|
||
| for (const auto* field : fields) { | ||
| ABSL_CHECK_EQ(field->containing_type(), descriptor_) |
There was a problem hiding this comment.
Why don't we use USAGE_CHECK_MESSAGE_TYPE here then? I think I slightly prefer more code reuse to a more specific error message.
c7640e2 to
0e41240
Compare
|
No problem! Switched to |
SwapFieldsImpl() validates that messages belong to this Reflection object, but does not check that each field descriptor in the fields vector actually belongs to the message's descriptor. When a field from a different message type is passed, its index can exceed the bounds of the target's offsets_ array, causing an out-of-bounds read that produces an arbitrary write offset for the subsequent std::swap. Add an ABSL_CHECK_EQ verifying field->containing_type() == descriptor_ for each field before processing, consistent with USAGE_CHECK_MESSAGE_TYPE used by all other Reflection write methods (SetInt32, SetString, etc.). Added regression test SwapFieldsForeignFieldCheck that verifies SwapFields rejects a field from a different message type.
0e41240 to
04bff40
Compare
β¦ory access (#30395) ## Summary `Reflection::HasOneof()`, `Reflection::ClearOneof()` and `Reflection::GetOneofFieldDescriptor()` compute the oneof-case address from the caller-supplied descriptor's index (via the private `GetOneofCase()`/`MutableOneofCase()` helpers and `ReflectionSchema::GetOneofCaseOffset()`, `generated_message_reflection.h:163-168`): ```cpp uint32_t GetOneofCaseOffset(const OneofDescriptor* oneof_descriptor) const { return static_cast<uint32_t>(oneof_case_offset_) + static_cast<uint32_t>(static_cast<size_t>(oneof_descriptor->index()) * sizeof(uint32_t)); } ``` None of the three methods validates that the descriptor belongs to the message they are called on, so a oneof descriptor from a different message type with a larger oneof index addresses memory outside the target object's oneof-case array: an out-of-bounds 4-byte read, and β in `ClearOneof()`, when the stale value resolves to a pointer-typed member of the target (string/message) β a `delete` of a value read from the message's oneof union, followed by a 4-byte write of 0 at the same offset. This is the oneof counterpart of the field-side check added to `SwapFieldsImpl` in #27289: every field-taking method validates `field->containing_type() == descriptor_` via `USAGE_CHECK_MESSAGE_TYPE`; there is no equivalent validation for oneof descriptors anywhere in the tree. (Private `GetOneofCase()`/`MutableOneofCase()` are additionally reached from field-based paths that are already checked.) ## Root cause - `HasOneof()` β `generated_message_reflection.cc:3383` (real-oneof branch) - `ClearOneof()` β `:3403`; the destroy path (`delete *MutableRaw<Message*>(...)`) at `:3439` - `GetOneofFieldDescriptor()` β `:3023` - offset computation β `ReflectionSchema::GetOneofCaseOffset()`, `generated_message_reflection.h:163-168` ## Fix Add a `USAGE_CHECK_ONEOF_MESSAGE_TYPE` guard mirroring `USAGE_CHECK_MESSAGE_TYPE`, reported through a oneof-specific reporter (`ReportReflectionUsageOneofError`; the existing reporter prints a `FieldDescriptor` name). The check runs at the top of all three public methods, fails fast in all build modes (it is not `NDEBUG`-gated, matching the field-side check), and is unreachable for the library's own internal callers, which always pass the message's own oneof descriptors. ## Test plan - New death test `GeneratedMessageReflectionTest.OneofForeignDescriptorCheck` β passes a `unittest::TestOneof2` oneof descriptor to `unittest::TestAllTypes` reflection for all three APIs and expects the usage error. - Full `//src/google/protobuf:generated_message_reflection_unittest` suite passes. - Verified on an ASan build of the runtime: before the change, the three calls show `heap-buffer-overflow` on the oneof-case read and `ClearOneof()` a `heap-use-after-free`; after the change each call terminates with the usage error instead. Closes #30395 COPYBARA_INTEGRATE_REVIEW=#30395 from kemrec:add-oneof-ownership-check aadb8ce PiperOrigin-RevId: 1015015987
Summary
Reflection::SwapFieldsImpl()validates that both messages belong to the correct Reflection object (GetReflection() == this), but does not verify that each field descriptor in thefieldsvector belongs to the message's own descriptor. This means a field from a different message type can be passed in, and itsindex()value can exceed the bounds of the target descriptor'soffsets_array.When this happens,
GetFieldOffset()atgenerated_message_reflection.h:127readsoffsets_[field->index()]out of bounds, producing an arbitrary offset value. The subsequentstd::swapthen writes 8 bytes at that offset relative to the message pointer, corrupting adjacent heap memory.Root cause
Every other
Reflectionwrite method (SetInt32,SetString,HasField,ClearField,Swap,RemoveLast,MutableRawRepeatedField, etc.) includesUSAGE_CHECK_MESSAGE_TYPEwhich validatesfield->containing_type() == descriptor_. TheSwapFieldsImplpath was the only write method missing this check.Fix
Add
ABSL_CHECK_EQ(field->containing_type(), descriptor_)for each field at the top of the loop inSwapFieldsImpl, before any field operations. This matches the validation pattern used by all other Reflection methods.Impact
Without this check, passing a foreign field descriptor causes:
offsets_[]array (reads heap data as a field offset)std::swapat the corrupted offset (overwrites adjacent heap objects)Test plan
SwapFieldsForeignFieldCheckdeath test: passes aForeignMessagefield toSwapFieldson aTestAllTypesmessage, verifies the check fires with "does not belong to message type"UsageErrorsdeath testSwapFields*tests should pass unchanged (they only use fields from the correct descriptor)