Skip to content

Carry the Onigmo regexp compiler fixes into 1.9.3 through 2.7.8 - #11

Merged
jeremy merged 3 commits into
mainfrom
onigmo-regexp-overflow-fixes
Oct 24, 2026
Merged

jeremy merged 3 commits into
mainfrom
onigmo-regexp-overflow-fixes

Conversation

@jeremy

@jeremy jeremy commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Backports two regexp compiler fixes into every Oniguruma or Onigmo Ruby here: 1.9.3-p551, 2.3.3, 2.3.8, 2.5.9 and 2.7.8. A pattern a user gets into Regexp.new can trigger either bug:

  • ruby/ruby#16801 (CVE-2026-110264): more than 32,767 range repeats or null checks wrap a signed 16-bit ID negative. The first match then writes out of bounds: Regexp.new("(?:foobar){0,100}" * 100000) =~ "x" segfaults.
  • ruby/ruby#19020: nested bounded repeats overflow the size arithmetic, so Regexp.new("(?:.{90000,}){90000}") never returns and ignores SIGTERM. Its second half caps the compiled program's size.

The 3.3.13, 3.4.12 and 4.0.9 releases fix both upstream. None of the EOL Rubies will get a release.

How

  • The patches live in patches/<major.minor>/ (1.9, 2.3, 2.5, 2.7; 2.3.3 and 2.3.8 share 2.3), one file per fix, each opening with a header that says what it fixes and where it came from.
  • Each affected definition gets one apply_patches build step. It applies its series in name order to the checksummed tarball with --fuzz=0, before configure.
  • This drops standalone installs for these five definitions. They read their patches from the checkout beside them, so a definition fetched on its own (the README's curl recipe) stops with an error naming the missing directory rather than build an unpatched Ruby. The README now says the curl recipe works for 1.8.7 only. Keeping the patches out of the definitions was the point of the move; inline heredocs had made them 3 to 5 times longer (2.7.8: 39 lines on main, 206 inline, 59 now).
  • The C is upstream's (493d26dc and the #19020 commits). Only the hunk context differs between versions.
  • test/build gets four checks against the installed Ruby: both #16801 overflows, the nested repeat (it must compile promptly and not match), and the program-size cap. A patch that's dropped, or that applies but no longer takes, fails the build.

Not backported:

  • ruby/ruby#13332 and Bug #22384 fix a double free that a later parse_subexp introduced. On these versions |{2,1} is already a plain RegexpError.
  • 1.8.7 predates Onigmo. Its engine caps quantifiers at 32,767 and a compiled pattern at 64 KB, and rejects all of these patterns with its own RegexpError. The checks accept those errors on 1.8.7 only, so they run on every version.

Verified

  • Patch application: all 6 tarballs match their pinned sha256, and every patch applies with --fuzz=0 under both BSD and GNU patch. The patch files are byte-identical to the heredocs they replaced (10 of 10).
  • Unpatched, all 5 crash on the #16801 pattern, and the #19020 pattern ignores SIGTERM until killed.
  • Patched, the #16801 patterns raise too many range repeat and too many null check, the nested repeat compiles in about 0.1 s, and 21475 repeats of .{100000,} raise too big compiled program. make test-all TESTS=ruby/test_regexp.rb gives 0 failures and the same test counts as the unpatched builds.
  • The checks catch a missing patch: all four fail on an unpatched 2.7.8. With only the #19020 patch, both #16801 checks fail.
  • A definition without its patches (copied elsewhere, or piped to ruby-build) stops before downloading anything.
  • bin/ci on 82318b9: lint clean, test/build all 12 of 12 (Ubuntu noble arm64, Arch x86_64), signed off.
  • test/build native all on 82318b9: 6 of 6 pass on macOS 27 arm64, the four regexp checks included.

For review

  1. Error codes on 2.3.x are -225 to -227, not upstream's -224 to -226: 2.3's Onigmo already uses -224 for ONIGERR_TOO_MANY_CAPTURE_GROUPS. Ruby shows only the message, so nothing visible changes.
  2. Patch order matters: the #16801 patch's context includes lines the #19020 patch changes, so 01- (#19020) has to come before 02- (#16801).

Not part of this change

  • On macOS, 1.9.3 through 2.5.9 report their platform as -darwin27, with the CPU name missing. Worth a separate look.

Copilot AI balanced review requested due to automatic review settings October 24, 2026 18:16
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-24T20:21:13.199746Z 82318b9 New commits
🔒 Security Review ✅ Completed 2026-10-24T20:25:21.856021Z 82318b9 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Security-sensitive C backports across five legacy runtimes warrant human validation of version compatibility and the reported build and regression results.

No open issues 🎉

What changed in this PR

Backports two regexp compiler fixes to five legacy Ruby build definitions, protecting against repeat-ID overflow and nested-repeat compilation hangs.

Changes:

  • Adds self-contained patches applied before configuration, with version-specific context and error codes.
  • Adds three installed-Ruby regression checks, including a deadline for nested-repeat compilation.
  • Documents the fixes, affected versions, and why Ruby 1.8.7 needs no backport.

[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

File Description
test/​build Adds regression checks for repeat IDs, null checks, and compilation hangs.
README.md Documents regexp fixes and version coverage.
2.7.8 Applies both compiler patches before configuration.
2.5.9 Applies both compiler patches before configuration.
2.3.8 Adds both patches with nonconflicting error codes.
2.3.3 Adds both patches with nonconflicting error codes.
1.9.3-p551 Adapts both patches to the older regexp engine.

Review effort: Balanced

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c555be3e0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread test/build Resolved
Comment thread test/build Resolved
Two regexp compiler bugs are reachable from any pattern a user can get
into Regexp.new, and every Onigmo-based Ruby here has both:

- More than 32,767 range repeats or null checks wrap their signed 16-bit
  ID negative, and a negative repeat ID indexes the match-time repeat
  stack: Regexp.new("(?:foobar){0,100}" * 100000) =~ "x" segfaults
  (ruby/ruby#16801, CVE-2026-110264).
- Nested bounded repeats overflow the int product that decides whether
  to unroll a repeat, so Regexp.new("(?:.{90000,}){90000}") never
  returns and ignores SIGTERM (ruby/ruby#19020).

Backport both as patch steps in each definition, applied to the
checksummed tarball with --fuzz=0 before configure. Inline heredocs, as
patch_lex_c99 does, so a definition still works fetched on its own. The
C is upstream's; only hunk context differs, plus the error codes on
2.3, whose Onigmo already spends -224 on ONIGERR_TOO_MANY_CAPTURE_GROUPS.

ruby/ruby#13332 and Bug #22384 don't apply: the double free they fix
came in with a later parse_subexp, and `|{2,1}` is already a plain
RegexpError here. 1.8.7 predates Onigmo; its engine caps quantifiers at
32,767 and a compiled pattern at 64 KB, and rejects all of these.

test/build now asserts each fix against the installed Ruby, so a patch
dropped from a step list, or one that applies and stops taking, fails
the build instead of shipping a Ruby that crashes on a hostile pattern.

ref: ruby/ruby#16801
ref: ruby/ruby#19020
ref: ruby/ruby#13332
ref: https://bugs.ruby-lang.org/issues/22384
The inline heredocs made each definition ~170 lines of C diff around
~40 lines of build recipe. Keep the patches as files in
patches/<major.minor>/, which 2.3.3 and 2.3.8 share, and give each
definition one apply_patches step that applies its series in name
order, still with --fuzz=0 before configure. Each patch file opens with
the explanation its heredoc had.

A definition finds its series beside itself through DEFINITION_PATH, so
it now needs the checkout. Fetched on its own, or piped in, it stops
with an error before downloading anything rather than build a Ruby
without the fixes. The README's single-file curl recipe now says it
covers 1.8.7 only. The test images copy patches/ next to the
definitions.

The patch bodies are byte-identical to the heredocs they replace.
The one #19020 check compiled the nested repeat and counted any
RegexpError as a pass. That covered only the size-arithmetic half of
the patch: once the arithmetic is fixed, that pattern makes a small
program and never reaches the new compiled-program size cap, so a
backport without the cap still passed. And a broken backport that
rejected the pattern outright passed too.

Split it in two, each in a child with a 30 s deadline:

- The nested repeat is a valid pattern. On 1.9.3 and up it must compile
  and not match "x"; only 1.8.7's engine may refuse it.
- 21475 repeats of .{100000,} must hit the size cap with "too big
  compiled program" (1.8.7: "too big quantifier"). Unpatched, they
  compile.

All four regexp checks pass on patched 1.8.7, 1.9.3, 2.3.8 and 2.7.8,
and all four fail on an unpatched 2.7.8.
@jeremy
jeremy force-pushed the onigmo-regexp-overflow-fixes branch from 3c555be to 82318b9 Compare October 24, 2026 20:19
@jeremy
jeremy requested a balanced review from Copilot October 24, 2026 20:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Security-sensitive compiler backports across five legacy Ruby versions require human confirmation of patch application and runtime results.

1 open issue

Review effort: Balanced

Comment thread README.md Resolved
@jeremy

jeremy commented Oct 24, 2026

Copy link
Copy Markdown
Member Author

Review pass on 82318b9:

  • Fixed: Codex's two test/build findings (size cap, nested repeat) and Copilot's stale description. All three threads are resolved.
  • Reviewers on this head: Codex (no findings) and Copilot.
  • CI: bin/ci passes 12 of 12 and is signed off. test/build native all passes 6 of 6.
  • Declined: none. Open for a decision: none.

@jeremy
jeremy merged commit 1d77ea1 into main Oct 24, 2026
2 checks passed
@jeremy
jeremy deleted the onigmo-regexp-overflow-fixes branch October 24, 2026 22:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants