From d7b3c686528b9ec5132b1063472b7bfef66b51bd Mon Sep 17 00:00:00 2001 From: prudhvi Date: Sun, 22 Mar 2026 22:55:55 +0530 Subject: [PATCH 1/3] 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 51f917499ecf4c4bbe164e740172ea5b4d9c87b8 Mon Sep 17 00:00:00 2001 From: prudhvi Date: Mon, 23 Mar 2026 12:15:13 +0530 Subject: [PATCH 2/3] 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. From f98a247b35010c87a6629d48b71ccb35e3f58400 Mon Sep 17 00:00:00 2001 From: prudhvi Date: Wed, 25 Mar 2026 14:55:12 +0530 Subject: [PATCH 3/3] Update version.rb --- lib/rubygems/version.rb | 58 ++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index abd359407f2d..b7966c3973f7 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -222,6 +222,7 @@ def initialize(version) end @version = -@version @segments = nil + @sort_key = compute_sort_key end ## @@ -350,44 +351,14 @@ def <=>(other) end return unless Gem::Version === other - return 0 if @version == other.version || canonical_segments == other.canonical_segments - - lhsegments = _segments - rhsegments = other._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 + # Fast path for comparison when available. + if @sort_key && other.sort_key + return @sort_key <=> other.sort_key end + return 0 if @version == other.version || canonical_segments == other.canonical_segments + lhsegments = canonical_segments rhsegments = other.canonical_segments @@ -395,6 +366,8 @@ def <=>(other) rhsize = rhsegments.size limit = (lhsize > rhsize ? rhsize : lhsize) + i = 0 + while i < limit lhs = lhsegments[i] rhs = rhsegments[i] @@ -449,6 +422,21 @@ 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.