Fix NULL-pointer dereference in upb_MiniTableEnum_CheckValue for unlinked closed-enum fields#26859
Open
parasol-aser wants to merge 1 commit intoprotocolbuffers:mainfrom
Open
Conversation
When upb_MiniTable_Build() produces a table with a closed-enum field, sub-enum pointers are not populated until a separate link step (e.g. upb_MiniTable_SetSubEnum / upb_MiniTable_Link). Decoding wire data for such an unlinked closed-enum field caused a SIGSEGV inside upb_MiniTableEnum_CheckValue on the varint scalar path (_upb_Decoder_DecodeWireValue) and the packed path (_upb_Decoder_DecodeEnumPacked). Guard both call sites with a NULL check on the sub-enum table. When the table is missing, treat the value (or every value, for packed) as an unknown field — the same behavior as when a value fails the closed-enum check. This matches the existing unlinked-sub-message handling in _upb_Decoder_CheckUnlinked(). The fix is applied at the decode-site boundary rather than inside the hot inline upb_MiniTableEnum_CheckValue to avoid regressing every correctly-linked caller. Amalgamated mirrors in ruby/ext and php/ext are patched identically so language bindings are not left vulnerable. Adds a regression test in upb/wire/decode_test.cc covering both the varint and packed paths using a MiniTable built directly via upb_MiniTable_Build() without linking the sub-enum. Fixes protocolbuffers#26857 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a reliably reproducible SIGSEGV in the UPB decoder when
upb_Decode()runs against aupb_MiniTableproduced byupb_MiniTable_Build()whose closed-enum field has not yet been linked to a sub-enum table. A 5-byte input (24 34 08 34 00) crashes the decoder deterministically by dereferencing a NULLupb_MiniTableEnum*insideupb_MiniTableEnum_CheckValue().By design,
upb_MiniTable_Build()does not populate sub-enum pointers — linking is a separate step viaupb_MiniTable_SetSubEnum()/upb_MiniTable_Link(). The decoder failed to account for this on two code paths:_upb_Decoder_DecodeWireValue()— varint (scalar) closed-enum path (upb/wire/decode.c:939)_upb_Decoder_DecodeEnumPacked()— packed closed-enum path (upb/wire/decode.c:351)Both sites called
upb_MiniTable_GetSubEnumTable(field)and passed the (possibly NULL) result straight intoupb_MiniTableEnum_CheckValue(), which unconditionally dereferencese->data[0]/e->mask_limit.Severity: High — remote DoS for deployments that build MiniTables from untrusted mini descriptors and then decode untrusted wire data against them. Not exploitable for code execution (deterministic NULL deref).
Fixes #26857
Fix
Guard both call sites with a NULL check on the sub-enum table, short-circuited with
||soupb_MiniTableEnum_CheckValue()is only invoked whene != NULL. Whene == NULL, treat the value (or every packed element) as an unknown field — the same behavior as when a value fails the closed-enum value check, and consistent with the existing unlinked-sub-message handling in_upb_Decoder_CheckUnlinked().The fix is applied at the decode-site boundary rather than inside the hot inline
upb_MiniTableEnum_CheckValue(), since its documented precondition is a non-NULL, linked enum table, and callers elsewhere alreadyUPB_ASSERTthis. Adding a NULL branch to the hot inline function would regress every correctly-linked caller.Changes
upb/wire/decode.c— add!e ||guard at both crash sites.ruby/ext/google/protobuf_c/ruby-upb.c— mirror the same guard in the amalgamated source so the Ruby extension is not left vulnerable.php/ext/google/protobuf/php-upb.c— same, for the PHP extension.upb/wire/decode_test.cc— add a regression test that builds a MiniTable with an unlinked closed-enum field viaupb_MiniTable_Build(), decodes both the varint-scalar reproducer and the packed-enum variant, and asserts no crash.upb/wire/BUILD— add//upb/baseand//upb/mini_descriptortest deps needed by the new test.The upb source and both amalgamated mirrors are updated in a single commit so that a Ruby/PHP release is not shipped with a knowingly-vulnerable copy of the decoder.
Test plan
bazel test //upb/wire:decode_testpasses.bazel test --config=asan //upb/wire:decode_testpasses (without the fix, ASAN reports NULL deref atupb/mini_table/internal/enum.h:30).bazel test --config=ubsan //upb/wire:decode_testpasses.bazel test //upb/...passes unchanged.ruby-upb.c.php-upb.c.HEAD~1under ASAN, confirm crash; run againstHEAD, confirm clean pass.🤖 Generated with Claude Code