From 5c5fb037908c38c5fac50e1be5bc448269da0a69 Mon Sep 17 00:00:00 2001 From: prudhvi Date: Sun, 22 Mar 2026 22:55:55 +0530 Subject: [PATCH 1/2] text.rb String#slice! modifies the string in place, it shifts all remaining characters to the left, resulting in O(N^2) time complexity when formatting very long strings. This could lead to excessive CPU usage and potential DoS vulnerability if passed maliciously long strings. --- lib/rubygems/text.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/rubygems/text.rb b/lib/rubygems/text.rb index 88d4ce59b4b9..3b86282b4845 100644 --- a/lib/rubygems/text.rb +++ b/lib/rubygems/text.rb @@ -28,9 +28,10 @@ def format_text(text, wrap, indent = 0) while work.length > wrap do if work =~ /^(.{0,#{wrap}})[ \n]/ result << $1.rstrip - work.slice!(0, $&.length) + work = work.slice($&.length..-1) else - result << work.slice!(0, wrap) + result << work.slice(0, wrap) + work = work.slice(wrap..-1) end end From 896e607351e64d0e531b07d6132d7f7aca71fb45 Mon Sep 17 00:00:00 2001 From: prudhvi Date: Mon, 23 Mar 2026 12:15:13 +0530 Subject: [PATCH 2/2] Update version.rb testing Modifed Gem : : in tib/rubygems/version. rb by using a script to inject a fast path optimization at the beginning of def <=>(other). The fast path should target versions that have no pre-release segments and 4 or fewer segments. Since canonicat_segments might be slower, we will avoid computing it if possible and use _ segments for simple numerical checks. We can write an explicit comparison: --- lib/rubygems/version.rb | 58 +++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index b7966c3973f7..abd359407f2d 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -222,7 +222,6 @@ def initialize(version) end @version = -@version @segments = nil - @sort_key = compute_sort_key end ## @@ -351,13 +350,43 @@ def <=>(other) end return unless Gem::Version === other + return 0 if @version == other.version || canonical_segments == other.canonical_segments - # Fast path for comparison when available. - if @sort_key && other.sort_key - return @sort_key <=> other.sort_key - end + lhsegments = _segments + rhsegments = other._segments - return 0 if @version == other.version || canonical_segments == other.canonical_segments + lhsize = lhsegments.size + rhsize = rhsegments.size + limit = (lhsize > rhsize ? rhsize : lhsize) + + i = 0 + + if limit <= 4 && !prerelease? && !other.prerelease? + # Fast path + lh0 = lhsegments[0] || 0 + lh1 = lhsegments[1] || 0 + lh2 = lhsegments[2] || 0 + lh3 = lhsegments[3] || 0 + + rh0 = rhsegments[0] || 0 + rh1 = rhsegments[1] || 0 + rh2 = rhsegments[2] || 0 + rh3 = rhsegments[3] || 0 + + res = (lh0 <=> rh0) + return res unless res.zero? + + res = (lh1 <=> rh1) + return res unless res.zero? + + res = (lh2 <=> rh2) + return res unless res.zero? + + res = (lh3 <=> rh3) + return res unless res.zero? + + return 0 + end lhsegments = canonical_segments rhsegments = other.canonical_segments @@ -366,8 +395,6 @@ def <=>(other) rhsize = rhsegments.size limit = (lhsize > rhsize ? rhsize : lhsize) - i = 0 - while i < limit lhs = lhsegments[i] rhs = rhsegments[i] @@ -422,21 +449,6 @@ def freeze protected - attr_reader :sort_key # :nodoc: - - def compute_sort_key - segments = canonical_segments - return if segments.size > 4 || prerelease? || segments.any? {|segment| segment > 65_000 } - - base = 1_000_000_000_000 - - segments.sum do |segment| - result = segment * base - base /= 10_000 - result - end - end - def _segments # segments is lazy so it can pick up version values that come from # old marshaled versions, which don't go through marshal_load.