mruby-regexp: do not truncate a POSIX bracket class name length - #7024
Merged
Conversation
`compile_charclass()` passed the length of a POSIX bracket class name to
`posix_class_bits()` through a `uint16_t` cast. The cast is silent, so a
name whose true length is congruent to a valid name's length modulo 65536
compared equal to that valid name, and the pattern compiled as the class
named by the first few bytes instead of raising.
```ruby
name = "alpha" + "A" * 65536 # 65541 bytes, (uint16_t)65541 == 5
re = Regexp.new("[[:#{name}:]]") # CRuby: RegexpError
re.match?("q") # mruby: true
re.match?("1") # mruby: false
```
The negated form goes down the same branch:
```ruby
name = "digit" + "A" * 65536
Regexp.new("[[:^#{name}:]]") # mruby: compiles as /[[:^digit:]]/
```
`NAME_IS` is `len == sizeof(s) - 1 && memcmp(name, s, len) == 0`, so the
truncated value decides both which literal can match and how many bytes
are compared. Nothing reads out of bounds: `memcmp()` stops at the
truncated length and the name lives in the pattern source. Only lengths
congruent to a valid name's length slip through; a name of exactly 65536
bytes truncates to 0, no `NAME_IS` test matches, and the pattern raises.
Take the length as `size_t` instead. Every over-long name then fails the
`len == sizeof(s) - 1` test, `posix_class_bits()` returns `FALSE`, and
the `compile_error()` already at the call site raises the same
`invalid POSIX bracket class` that `[[:bogus:]]` raises.
|
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)
📝 WalkthroughWalkthroughThe regexp compiler now passes POSIX bracket-class name lengths as ChangesPOSIX class length handling
Estimated code review effort: 2 (Simple) | ~10 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 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.
compile_charclass()passes the length of a POSIX bracket class name toposix_class_bits()through auint16_tcast. The cast is silent, so a namewhose true length is congruent to a valid name's length modulo 65536 is
accepted, and the pattern compiles as the class named by the first few bytes
rather than raising
RegexpError.The name that was written is not a POSIX bracket class name. mruby compares
only its first five bytes, finds
"alpha", and silently accepts the other65536. The negated form goes down the same branch:
Only lengths congruent to a valid name's length slip through. A name of
exactly 65536 bytes truncates to 0, no
NAME_IStest matches a zero length,and the pattern raises as it should.
Cause
nameis set right after[:and any^, and the scan runs to the first:or
], soc->p - nameis aptrdiff_tover the pattern source with no upperbound; nothing in
mrb_re_compile()limits the pattern length. The length isthen narrowed at the call site:
posix_class_bits()takes the length asuint16_t, and itsNAME_ISmacro isso the truncated value decides both which literal can match and how many bytes
are compared. With the truncation,
len == 5andmemcmp(name, "alpha", 5)succeeds,
posix_class_bits()returnsTRUE, and thecompile_error()neverfires. The
:and]are then consumed and the bits are merged into theclass, so the trailing filler bytes leave no trace in the compiled pattern.
This stays in bounds.
memcmp()compares only the truncated number of bytes,and the name lives in the pattern source, so nothing reads past an allocation.
The bug is a compatibility one: a pattern CRuby rejects compiles here, and it
compiles as something the pattern does not say.
CRuby 4.0.6 raises for both patterns above, with
invalid POSIX bracket typefollowed by the whole pattern. That is Onigmo's
parse_posix_bracket(): on aprefix hit it requires the next two bytes to be
:], which they are not here.Fix
Change
posix_class_bits()'slentosize_tand drop the cast.NAME_ISthen compares the true length, every over-long name fails the
len == sizeof(s) - 1test,posix_class_bits()falls through toreturn FALSE, and thecompile_error()already at the call site raises withno new call site and the same message
[[:bogus:]]produces. Theshort-circuit in
NAME_ISkeepsmemcmp()bounded by the literal, so thebody of the function needs no other edit.
Guarding the
ptrdiff_tagainstUINT16_MAXbefore the cast would work too,but removing the narrowing is preferable to making it loud, and it costs one
parameter type.
Tests
Two cases in
assert("Regexp - POSIX bracket classes"), next to the existingassert_raise(RegexpError) { Regexp.new("[[:bogus:]]") }, covering the plainand negated over-long names. The block's existing assertions cover the
regression side: a valid name must keep compiling.
rake testpasses onx86_64-linuxwith no new compiler warning.Summary by CodeRabbit
Bug Fixes
RegexpErrorinstead of being silently truncated or compiled incorrectly.Tests