Repository navigation
Fix match registers when position is larger than INT_MAX - #220
Merged
Merged
Conversation
kou
reviewed
Oct 6, 2026
| extract_range(struct strscanner *p, long beg_i, long end_i) | ||
| { | ||
| if (beg_i > S_LEN(p)) return Qnil; | ||
| if (beg_i < 0 || beg_i > S_LEN(p)) return Qnil; |
Member
There was a problem hiding this comment.
It seems that we don't need < 0 check with the adjust_registers_to_matched() change. Can we revert this change?
| extract_beg_len(struct strscanner *p, long beg_i, long len) | ||
| { | ||
| if (beg_i > S_LEN(p)) return Qnil; | ||
| if (beg_i < 0 || beg_i > S_LEN(p)) return Qnil; |
Member
There was a problem hiding this comment.
It seems that beg_i < 0 is never true with/without the adjust_registers_to_matched() . Can we revert this change?
Comment on lines
+1267
to
+1286
| def test_pos_over_int_max | ||
| # Regression: scan positions beyond INT_MAX were truncated to a negative | ||
| # int when storing match registers in fixed_anchor mode, which made | ||
| # getch/get_byte read out of bounds instead of returning the byte. | ||
| # Needs a string longer than INT_MAX: skip when memory is tight. | ||
| begin | ||
| string = "a" * (2**31 + 64) | ||
| rescue NoMemoryError | ||
| skip "requires more than 2GB of memory" | ||
| end | ||
| pos = 2**31 + 10 | ||
|
|
||
| scanner = create_string_scanner(string) | ||
| scanner.pos = pos | ||
| assert_equal("a", scanner.getch) | ||
|
|
||
| scanner = create_string_scanner(string) | ||
| scanner.pos = pos | ||
| assert_equal("a", scanner.get_byte) | ||
| end |
Member
There was a problem hiding this comment.
I don't want to allocate 2GB memory in test. Could you remove this? We have a reproduce code in the description. It's enough.
adjust_registers_to_matched() narrowed 64-bit scan positions to int via onig_region_set(), so pos > INT_MAX produced negative match registers and getch/get_byte read out of bounds in fixed_anchor mode. Store the positions directly in the region instead.
Gkasgd
force-pushed
the
fix-fixed-anchor-pos-over-int-max
branch
from
October 6, 2026 02:57
4b3292c to
851c879
Compare
Contributor
Author
|
Thanks for the review! Both done:
Re-pushed as 851c879; diff is |
Member
|
Thanks. |
matzbot
pushed a commit
to ruby/ruby
that referenced
this pull request
Oct 6, 2026
INT_MAX (ruby/strscan#220) ## What With `StringScanner.new(str, fixed_anchor: true)`, a scan position beyond `INT_MAX` (a string longer than 2 GiB) makes `StringScanner#getch` / `#get_byte` read out of bounds: a deterministic `SIGSEGV` (and a bogus string returned when the address happens to be mapped) instead of returning the byte. ## Reproducer ```ruby require "strscan" len = 2**31 + 64 # > INT_MAX pos = 2**31 + 10 s = "a" * len sc = StringScanner.new(s, fixed_anchor: true) sc.pos = pos p sc.getch # expected "a"; actual: SIGSEGV (exit 139) ``` On current master (`STRSCAN_VERSION 3.1.9`, built against Ruby 3.3.8): ``` control: fixed_anchor=false getch OK size=1 matched=1 trigger: calling getch with fixed_anchor=true ... [BUG] Segmentation fault strscan.so(extract_range) strscan.c:170 strscan.so(strscan_getch) strscan.c:1204 ruby exit code: 139 ``` With the fix the same script completes normally and returns `"a"`. ## Root cause `adjust_registers_to_matched()` stored the (64-bit) scan positions through `onig_region_set(region, 0, (int)p->prev, (int)p->curr)` — the `int` parameters silently narrow the positions. For `pos > INT_MAX` the stored `beg[0]` becomes negative; `extract_range()` / `extract_beg_len()` only checked the upper bound, so `S_PBEG(p) + beg_i` is computed before the string and `rb_str_new()` reads out of bounds. ## Changes - store the match registers directly (`OnigPosition` values, no narrowing). ## Tests - Reproducer: crashes before the change / returns `"a"` after. - `test/strscan/test_stringscanner.rb`: no new failures vs master (only the pre-existing standalone-harness issues). ## Notes This was also reported through HackerOne (`#4061020`), where Hiroshi SHIBATA closed it as Informative (the effect is a crash that requires an application-controlled buffer larger than 2 GiB, rather than a security vulnerability) and asked to send it here as a pull request. ruby/strscan@d976e3cbfc Co-authored-by: Gkasgd <7713860+Gkasgd@users.noreply.github.com>
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.
What
With
StringScanner.new(str, fixed_anchor: true), a scan position beyondINT_MAX(a string longer than 2 GiB) makesStringScanner#getch/#get_byteread out of bounds: a deterministicSIGSEGV(and a bogus string returned when the address happens to be mapped) instead of returning the byte.Reproducer
On current master (
STRSCAN_VERSION 3.1.9, built against Ruby 3.3.8):With the fix the same script completes normally and returns
"a".Root cause
adjust_registers_to_matched()stored the (64-bit) scan positions throughonig_region_set(region, 0, (int)p->prev, (int)p->curr)— theintparameters silently narrow thepositions. For
pos > INT_MAXthe storedbeg[0]becomes negative;extract_range()/extract_beg_len()only checked the upper bound, soS_PBEG(p) + beg_iis computed before thestring and
rb_str_new()reads out of bounds.Changes
OnigPositionvalues, no narrowing).Tests
"a"after.test/strscan/test_stringscanner.rb: no new failures vs master (only the pre-existingstandalone-harness issues).
Notes
This was also reported through HackerOne (
#4061020), where Hiroshi SHIBATA closed it asInformative (the effect is a crash that requires an application-controlled buffer larger than
2 GiB, rather than a security vulnerability) and asked to send it here as a pull request.