mruby-regexp: fix a heap-buffer-overflow read in MatchData#[] for an over-long group name - #7002
Merged
Merged
Conversation
`matchdata_aref()` compared the requested name length truncated to `uint16_t` but passed the untruncated length to `memcmp()`, so a name whose length is congruent to a stored name's length modulo 65536 passed the length test and then read far past the end of the named-capture arena. A stored name can never be longer than `UINT16_MAX`, so rejecting such a request before the loop excludes nothing that could have matched and makes the cast inside the loop lossless. The overread is invisible without a sanitizer, so the test pins the path rather than the result: the lookup raises `IndexError` for this name both before and after the guard.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthrough
ChangesNamed-capture lookup validation
Estimated code review effort: 1 (Trivial) | ~5 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This was referenced Aug 3, 2026
This was referenced Aug 9, 2026
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.
MatchData#[]reads past the named-capture arena when it is given a group namelonger than 65535 bytes.
matchdata_aref()compares the requested name against each named capture bytesting the lengths first and then calling
memcmp(). The length test truncatesthe requested length to
uint16_t, but thememcmp()next to it uses theuntruncated length, so a name whose length is congruent to a stored name's
length modulo 65536 passes the guard and then reads far past the end of the
stored name.
The stored name lives in the pattern's named-capture arena, which holds exactly
the three bytes
abchere, somemcmp()is handed a 3-byte allocation and a65539-byte length.
No C API is involved: the reproducer is two lines of ordinary Ruby, so this is a
VM crash from valid Ruby code rather than a compatibility gap. On a build without
a sanitizer it is invisible, because
memcmp()stops at the first differingbyte; how far past the allocation it reads depends on how many bytes of
unrelated heap happen to match, and the arena is only as large as the sum of the
pattern's group names.
Cause
name_lenis anmrb_inttaken fromRSTRING_LEN()ormrb_sym_name_len(),while
re_named_capture::name_lenis auint16_t(
mrbgems/mruby-regexp/include/re_internal.h:63). The cast makes the twocomparable, and the truncated value is then never used again:
Fix
Reject a length that cannot name a group before the loop runs:
That is the whole change. A stored name can never be longer than
UINT16_MAX,so nothing that could have matched is excluded, and the truncating cast inside
the loop becomes lossless. A name this long resolves to no group either way, so
it takes the
IndexErrorpath that #7000 put in place, both before and afterthis change.
UINT16_MAXcomes from<stdint.h>, whichre_internal.halready includes.The change adds no VM call: name-to-group resolution stays entirely in C, which
is where
CONTRIBUTING.mdasks that an internal-representation lookup live.Test
The overread cannot be observed from Ruby, so the test pins the path rather than
the result, and fails only on a sanitizer build. Added to
mrbgems/mruby-regexp/test/regexp.rb:Verified on
c78448d5bwith clangaddress,undefined(
MRUBY_CONFIG=build_config/asan.rb):rake testaborts on it with theheap-buffer-overflowabove, pointing at
regexp.c:598.2103 tests, 0 failures, 0 crashes, plus 78 bintests.
Not the same bug: the compile-side truncation
re_compile.ctruncates a group name's length touint16_tin two places, atcapture registration (
:668) and at\k<name>resolution (:814). Those twotruncate consistently and the arena is built from the truncated lengths
(
:1292), so no read leaves its allocation. A(?<name>...)or\k<name>whosename exceeds 65535 bytes can resolve to the wrong group, but nothing reads out of
bounds, and a pattern that long is not worth a guard of its own. Only the
MatchData#[]site mixes a truncated length with an untruncated one.Relationship to #7000
Independent of #7000, which rewrote the same function and has since merged. The
two changes sat in separate hunks and this one applies unchanged on top. What
#7000 changed here is only the observable result for such a name:
nilbeforeit,
IndexErrorafter, and the overread is gone from either point on. Keptseparate on purpose, since
CONTRIBUTING.mdasks that a pull request not mixseveral bugfixes and a memory-safety fix should be reviewable on its own.
Summary by CodeRabbit
IndexErrorinstead of being incorrectly matched.