CVE-2017-0899: Improper Control of Generation of Code ('Code Injection') (CWE-94)
value: RubyGems version 2.6.12 and earlier is vulnerable to maliciously crafted gem specifications that include terminal escape characters. Printing the gem specification would execute terminal escape sequences.
CWE description: The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
CVSS v3 base score: 9.8
Published: 2017-08-31T20:29Z
Repository: https://github.com/rubygems/rubygems
Language: Ruby
Files changed: lib/rubygems/commands/query_command.rb, lib/rubygems/text.rb, test/rubygems/test_gem_commands_query_command.rb, test/rubygems/test_gem_text.rb
Commit date: 2017-08-27 20:37:15 -0500
Commit message: Clean any text present in gems before displaying it

This makes sure that any data pulled out of a gem spec is cleaned of all
non-printable characters before printing it. This prevents a gem from
causing havoc with a users terminal.

Diff: diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb
index 70f81272..4ed957b9 100644
--- a/lib/rubygems/commands/query_command.rb
+++ b/lib/rubygems/commands/query_command.rb
@@ -224,11 +224,11 @@ def output_versions output, versions
           seen[n.version] = true
           false
         end
       end
 
-      output << make_entry(matching_tuples, platforms)
+      output << clean_text(make_entry(matching_tuples, platforms))
     end
   end
 
   def entry_details entry, detail_tuple, specs, platforms
     return unless options[:details]
diff --git a/lib/rubygems/text.rb b/lib/rubygems/text.rb
index 732f1b99..edb76340 100644
--- a/lib/rubygems/text.rb
+++ b/lib/rubygems/text.rb
@@ -4,17 +4,24 @@
 ##
 # A collection of text-wrangling methods
 
 module Gem::Text
 
+  ##
+  # Remove any non-printable characters and make the text suitable for
+  # printing.
+  def clean_text(text)
+    text.gsub(/[\u0000-\u0008\u000b-\u000c\u000e-\u001F\u007f]/, ".".freeze)
+  end
+
   ##
   # Wraps +text+ to +wrap+ characters and optionally indents by +indent+
   # characters
 
   def format_text(text, wrap, indent=0)
     result = []
-    work = text.dup
+    work = clean_text(text)
 
     while work.length > wrap do
       if work =~ /^(.{0,#{wrap}})[ \n]/ then
         result << $1.rstrip
         work.slice!(0, $&.length)
diff --git a/test/rubygems/test_gem_commands_query_command.rb b/test/rubygems/test_gem_commands_query_command.rb
index d8d682b1..2a87601c 100644
--- a/test/rubygems/test_gem_commands_query_command.rb
+++ b/test/rubygems/test_gem_commands_query_command.rb
@@ -114,10 +114,50 @@ def test_execute_details
     Homepage: http://a.example.com/
 
     This is a lot of text. This is a lot of text. This is a lot of text.
     This is a lot of text.
 
+pl (1)
+    Platform: i386-linux
+    Author: A User
+    Homepage: http://example.com
+
+    this is a summary
+    EOF
+
+    assert_equal expected, @ui.output
+    assert_equal '', @ui.error
+  end
+
+  def test_execute_details_cleans_text
+    spec_fetcher do |fetcher|
+      fetcher.spec 'a', 2 do |s|
+        s.summary = 'This is a lot of text. ' * 4
+        s.authors = ["Abraham Lincoln \u0001", "\u0002 Hirohito"]
+        s.homepage = "http://a.example.com/\u0003"
+      end
+
+      fetcher.legacy_platform
+    end
+
+    @cmd.handle_options %w[-r -d]
+
+    use_ui @ui do
+      @cmd.execute
+    end
+
+    expected = <<-EOF
+
+*** REMOTE GEMS ***
+
+a (2)
+    Authors: Abraham Lincoln ., . Hirohito
+    Homepage: http://a.example.com/.
+
+    This is a lot of text. This is a lot of text. This is a lot of text.
+    This is a lot of text.
+
 pl (1)
     Platform: i386-linux
     Author: A User
     Homepage: http://example.com
 
diff --git a/test/rubygems/test_gem_text.rb b/test/rubygems/test_gem_text.rb
index a6e22e04..4875f37c 100644
--- a/test/rubygems/test_gem_text.rb
+++ b/test/rubygems/test_gem_text.rb
@@ -34,10 +34,14 @@ def test_format_text_trailing # for two spaces after .
     EXPECTED
 
     assert_equal expected, format_text(text, 78)
   end
 
+  def test_format_removes_nonprintable_characters
+    assert_equal "text with weird .. stuff", format_text("text with weird \u001b\u0002 stuff", 40)
+  end
+
   def test_min3
     assert_equal 1, min3(1, 1, 1)
     assert_equal 1, min3(1, 1, 2)
     assert_equal 1, min3(1, 2, 1)
     assert_equal 1, min3(2, 1, 1)

Vulnerable code:       output << make_entry(matching_tuples, platforms)
    work = text.dup
Fixed code:       output << clean_text(make_entry(matching_tuples, platforms))
  ##
  # Remove any non-printable characters and make the text suitable for
  # printing.
  def clean_text(text)
    text.gsub(/[\u0000-\u0008\u000b-\u000c\u000e-\u001F\u007f]/, ".".freeze)
  end

    work = clean_text(text)
pl (1)
    Platform: i386-linux
    Author: A User
    Homepage: http://example.com

    this is a summary
    EOF

    assert_equal expected, @ui.output
    assert_equal '', @ui.error
  end

  def test_execute_details_cleans_text
    spec_fetcher do |fetcher|
      fetcher.spec 'a', 2 do |s|
        s.summary = 'This is a lot of text. ' * 4
        s.authors = ["Abraham Lincoln \u0001", "\u0002 Hirohito"]
        s.homepage = "http://a.example.com/\u0003"
      end

      fetcher.legacy_platform
    end

    @cmd.handle_options %w[-r -d]

    use_ui @ui do
      @cmd.execute
    end

    expected = <<-EOF

*** REMOTE GEMS ***

a (2)
    Authors: Abraham Lincoln ., . Hirohito
    Homepage: http://a.example.com/.

    This is a lot of text. This is a lot of text. This is a lot of text.
    This is a lot of text.

  def test_format_removes_nonprintable_characters
    assert_equal "text with weird .. stuff", format_text("text with weird \u001b\u0002 stuff", 40)
  end