CVE-2018-1000075: Loop with Unreachable Exit Condition ('Infinite Loop') (CWE-835)
value: RubyGems version Ruby 2.2 series: 2.2.9 and earlier, Ruby 2.3 series: 2.3.6 and earlier, Ruby 2.4 series: 2.4.3 and earlier, Ruby 2.5 series: 2.5.0 and earlier, prior to trunk revision 62422 contains a infinite loop caused by negative size vulnerability in ruby gem package tar header that can result in a negative size could cause an infinite loop.. This vulnerability appears to have been fixed in 2.7.6.
CWE description: The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop.
CVSS v3 base score: 7.5
Published: 2018-03-13T15:29Z
Repository: https://github.com/rubygems/rubygems
Language: Ruby
Files changed: lib/rubygems/package/tar_header.rb, test/rubygems/test_gem_package_tar_header.rb
Commit date: 2018-02-15 22:19:03 -0800
Commit message: Strictly interpret octal fields in tar headers

Any octal field that contains characters other that 0-7 will cause acn
exception to be raised.

This prevents a negative size from being set, which could cause an
infinite loop.

Diff: diff --git a/lib/rubygems/package/tar_header.rb b/lib/rubygems/package/tar_header.rb
index c54bd14d57..d557357114 100644
--- a/lib/rubygems/package/tar_header.rb
+++ b/lib/rubygems/package/tar_header.rb
@@ -102,29 +102,34 @@ def self.from(stream)
     empty = (header == "\0" * 512)
 
     fields = header.unpack UNPACK_FORMAT
 
     new :name     => fields.shift,
-        :mode     => fields.shift.oct,
-        :uid      => fields.shift.oct,
-        :gid      => fields.shift.oct,
-        :size     => fields.shift.oct,
-        :mtime    => fields.shift.oct,
-        :checksum => fields.shift.oct,
+        :mode     => strict_oct(fields.shift),
+        :uid      => strict_oct(fields.shift),
+        :gid      => strict_oct(fields.shift),
+        :size     => strict_oct(fields.shift),
+        :mtime    => strict_oct(fields.shift),
+        :checksum => strict_oct(fields.shift),
         :typeflag => fields.shift,
         :linkname => fields.shift,
         :magic    => fields.shift,
-        :version  => fields.shift.oct,
+        :version  => strict_oct(fields.shift),
         :uname    => fields.shift,
         :gname    => fields.shift,
-        :devmajor => fields.shift.oct,
-        :devminor => fields.shift.oct,
+        :devmajor => strict_oct(fields.shift),
+        :devminor => strict_oct(fields.shift),
         :prefix   => fields.shift,
 
         :empty => empty
   end
 
+  def self.strict_oct(str)
+    return str.oct if str =~ /\A[0-7]*\z/
+    raise ArgumentError, "#{str.inspect} is not an octal string"
+  end
+
   ##
   # Creates a new TarHeader using +vals+
 
   def initialize(vals)
     unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode] then
diff --git a/test/rubygems/test_gem_package_tar_header.rb b/test/rubygems/test_gem_package_tar_header.rb
index d33877057d..a0719a7531 100644
--- a/test/rubygems/test_gem_package_tar_header.rb
+++ b/test/rubygems/test_gem_package_tar_header.rb
@@ -141,7 +141,27 @@ def test_update_checksum
     @tar_header.update_checksum
 
     assert_equal '012467', @tar_header.checksum
   end
 
+  def test_from_bad_octal
+    test_cases = [
+      "00000006,44\000", # bogus character
+      "00000006789\000", # non-octal digit
+      "+0000001234\000", # positive sign
+      "-0000001000\000", # negative sign
+      "0x000123abc\000", # radix prefix
+    ]
+
+    test_cases.each do |val|
+      header_s = @tar_header.to_s
+      # overwrite the size field
+      header_s[124, 12] = val
+      io = TempIO.new header_s
+      assert_raises ArgumentError do
+        new_header = Gem::Package::TarHeader.from io
+      end
+    end
+  end
+
 end
 

Vulnerable code:         :mode     => fields.shift.oct,
        :uid      => fields.shift.oct,
        :gid      => fields.shift.oct,
        :size     => fields.shift.oct,
        :mtime    => fields.shift.oct,
        :checksum => fields.shift.oct,
        :version  => fields.shift.oct,
        :devmajor => fields.shift.oct,
        :devminor => fields.shift.oct,
Fixed code:         :mode     => strict_oct(fields.shift),
        :uid      => strict_oct(fields.shift),
        :gid      => strict_oct(fields.shift),
        :size     => strict_oct(fields.shift),
        :mtime    => strict_oct(fields.shift),
        :checksum => strict_oct(fields.shift),
        :version  => strict_oct(fields.shift),
        :devmajor => strict_oct(fields.shift),
        :devminor => strict_oct(fields.shift),
  def self.strict_oct(str)
    return str.oct if str =~ /\A[0-7]*\z/
    raise ArgumentError, "#{str.inspect} is not an octal string"
  end

  def test_from_bad_octal
    test_cases = [
      "00000006,44\000", # bogus character
      "00000006789\000", # non-octal digit
      "+0000001234\000", # positive sign
      "-0000001000\000", # negative sign
      "0x000123abc\000", # radix prefix
    ]

    test_cases.each do |val|
      header_s = @tar_header.to_s
      # overwrite the size field
      header_s[124, 12] = val
      io = TempIO.new header_s
      assert_raises ArgumentError do
        new_header = Gem::Package::TarHeader.from io
      end
    end
  end