File: 0018-fix-rubygem-vulnerabilities.patch

package info (click to toggle)
jruby 9.1.17.0-3
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 71,608 kB
  • sloc: ruby: 505,916; java: 237,875; xml: 31,161; ansic: 7,152; yacc: 4,605; sh: 887; makefile: 108; jsp: 48; tcl: 40
file content (424 lines) | stat: -rw-r--r-- 13,877 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
From: Hideki Yamane <henrich@debian.org>
Date: Wed, 29 May 2019 07:54:22 +0900
Subject: fix rubygem vulnerabilities

Bug-Debian: https://bugs.debian.org/925987
Origin: vendor, https://bugs.ruby-lang.org/attachments/7669
Forwarded: not-needed
Last-Update: 2019-05-29

fix below CVEs
CVE-2019-8320
CVE-2019-8321
CVE-2019-8322
CVE-2019-8323
CVE-2019-8324
CVE-2019-8325

see https://blog.rubygems.org/2019/03/05/security-advisories-2019-03.html
---
 lib/ruby/stdlib/rubygems/command_manager.rb        |  10 +-
 lib/ruby/stdlib/rubygems/commands/owner_command.rb |   5 +-
 lib/ruby/stdlib/rubygems/gemcutter_utilities.rb    |   7 +-
 lib/ruby/stdlib/rubygems/installer.rb              |  29 +++++-
 lib/ruby/stdlib/rubygems/package.rb                |  10 ++
 lib/ruby/stdlib/rubygems/user_interaction.rb       |   7 +-
 test/mri/rubygems/test_gem_installer.rb            | 108 +++++++++++++++++++++
 test/mri/rubygems/test_gem_package.rb              |  36 +++++++
 test/mri/rubygems/test_gem_text.rb                 |   5 +
 9 files changed, 203 insertions(+), 14 deletions(-)

diff --git a/lib/ruby/stdlib/rubygems/command_manager.rb b/lib/ruby/stdlib/rubygems/command_manager.rb
index 451b719..d3ff661 100644
--- a/lib/ruby/stdlib/rubygems/command_manager.rb
+++ b/lib/ruby/stdlib/rubygems/command_manager.rb
@@ -7,6 +7,7 @@
 
 require 'rubygems/command'
 require 'rubygems/user_interaction'
+require 'rubygems/text'
 
 ##
 # The command manager registers and installs all the individual sub-commands
@@ -32,6 +33,7 @@ require 'rubygems/user_interaction'
 
 class Gem::CommandManager
 
+  include Gem::Text
   include Gem::UserInteraction
 
   BUILTIN_COMMANDS = [ # :nodoc:
@@ -138,12 +140,12 @@ class Gem::CommandManager
   def run(args, build_args=nil)
     process_args(args, build_args)
   rescue StandardError, Timeout::Error => ex
-    alert_error "While executing gem ... (#{ex.class})\n    #{ex}"
+    alert_error clean_text("While executing gem ... (#{ex.class})\n    #{ex}")
     ui.backtrace ex
 
     terminate_interaction(1)
   rescue Interrupt
-    alert_error "Interrupted"
+    alert_error clean_text("Interrupted")
     terminate_interaction(1)
   end
 
@@ -161,7 +163,7 @@ class Gem::CommandManager
       say Gem::VERSION
       terminate_interaction 0
     when /^-/ then
-      alert_error "Invalid option: #{args.first}.  See 'gem --help'."
+      alert_error clean_text("Invalid option: #{args.first}. See 'gem --help'.")
       terminate_interaction 1
     else
       cmd_name = args.shift.downcase
@@ -210,7 +212,7 @@ class Gem::CommandManager
     rescue Exception => e
       e = load_error if load_error
 
-      alert_error "Loading command: #{command_name} (#{e.class})\n\t#{e}"
+      alert_error clean_text("Loading command: #{command_name} (#{e.class})\n\t#{e}")
       ui.backtrace e
     end
   end
diff --git a/lib/ruby/stdlib/rubygems/commands/owner_command.rb b/lib/ruby/stdlib/rubygems/commands/owner_command.rb
index 2ee7f84..7842a32 100644
--- a/lib/ruby/stdlib/rubygems/commands/owner_command.rb
+++ b/lib/ruby/stdlib/rubygems/commands/owner_command.rb
@@ -2,8 +2,11 @@
 require 'rubygems/command'
 require 'rubygems/local_remote_options'
 require 'rubygems/gemcutter_utilities'
+require 'rubygems/text'
 
 class Gem::Commands::OwnerCommand < Gem::Command
+
+  include Gem::Text
   include Gem::LocalRemoteOptions
   include Gem::GemcutterUtilities
 
@@ -62,7 +65,7 @@ permission to.
     end
 
     with_response response do |resp|
-      owners = Gem::SafeYAML.load resp.body
+      owners = Gem::SafeYAML.load clean_text(resp.body)
 
       say "Owners for gem: #{name}"
       owners.each do |owner|
diff --git a/lib/ruby/stdlib/rubygems/gemcutter_utilities.rb b/lib/ruby/stdlib/rubygems/gemcutter_utilities.rb
index 7c6d6bb..623d930 100644
--- a/lib/ruby/stdlib/rubygems/gemcutter_utilities.rb
+++ b/lib/ruby/stdlib/rubygems/gemcutter_utilities.rb
@@ -1,11 +1,14 @@
 # frozen_string_literal: true
 require 'rubygems/remote_fetcher'
+require 'rubygems/text'
 
 ##
 # Utility methods for using the RubyGems API.
 
 module Gem::GemcutterUtilities
 
+  include Gem::Text
+
   # TODO: move to Gem::Command
   OptionParser.accept Symbol do |value|
     value.to_sym
@@ -145,13 +148,13 @@ module Gem::GemcutterUtilities
       if block_given? then
         yield response
       else
-        say response.body
+        say clean_text(response.body)
       end
     else
       message = response.body
       message = "#{error_prefix}: #{message}" if error_prefix
 
-      say message
+      say clean_text(message)
       terminate_interaction 1 # TODO: question this
     end
   end
diff --git a/lib/ruby/stdlib/rubygems/installer.rb b/lib/ruby/stdlib/rubygems/installer.rb
index 6fd3399..5818b94 100644
--- a/lib/ruby/stdlib/rubygems/installer.rb
+++ b/lib/ruby/stdlib/rubygems/installer.rb
@@ -697,9 +697,26 @@ class Gem::Installer
       unpack or File.writable?(gem_home)
   end
 
-  def verify_spec_name
-    return if spec.name =~ Gem::Specification::VALID_NAME_PATTERN
-    raise Gem::InstallError, "#{spec} has an invalid name"
+  def verify_spec
+    unless spec.name =~ Gem::Specification::VALID_NAME_PATTERN
+      raise Gem::InstallError, "#{spec} has an invalid name"
+    end
+
+    if spec.raw_require_paths.any?{|path| path =~ /\r\n|\r|\n/ }
+      raise Gem::InstallError, "#{spec} has an invalid require_paths"
+    end
+
+    if spec.extensions.any?{|ext| ext =~ /\r\n|\r|\n/ }
+      raise Gem::InstallError, "#{spec} has an invalid extensions"
+    end
+
+    unless spec.specification_version.to_s =~ /\A\d+\z/
+      raise Gem::InstallError, "#{spec} has an invalid specification_version"
+    end
+
+    if spec.dependencies.any? {|dep| dep.type =~ /\r\n|\r|\n/ || dep.name =~ /\r\n|\r|\n/ }
+      raise Gem::InstallError, "#{spec} has an invalid dependencies"
+    end
   end
 
   ##
@@ -826,9 +843,11 @@ TEXT
   def pre_install_checks
     verify_gem_home options[:unpack]
 
-    ensure_loadable_spec
+    # The name and require_paths must be verified first, since it could contain
+    # ruby code that would be eval'ed in #ensure_loadable_spec
+    verify_spec
 
-    verify_spec_name
+    ensure_loadable_spec
 
     if options[:install_as_default]
       Gem.ensure_default_gem_subdirectories gem_home
diff --git a/lib/ruby/stdlib/rubygems/package.rb b/lib/ruby/stdlib/rubygems/package.rb
index b5a5fe2..e4e4545 100644
--- a/lib/ruby/stdlib/rubygems/package.rb
+++ b/lib/ruby/stdlib/rubygems/package.rb
@@ -425,6 +425,16 @@ EOM
     raise Gem::Package::PathError.new(destination, destination_dir) unless
       destination.start_with? destination_dir + '/'
 
+    begin
+      real_destination = File.expand_path(File.realpath(destination))
+    rescue
+      # it's fine if the destination doesn't exist, because rm -rf'ing it can't cause any damage
+      nil
+    else
+      raise Gem::Package::PathError.new(real_destination, destination_dir) unless
+        real_destination.start_with? destination_dir + '/'
+    end
+
     destination.untaint
     destination
   end
diff --git a/lib/ruby/stdlib/rubygems/user_interaction.rb b/lib/ruby/stdlib/rubygems/user_interaction.rb
index 390d0f2..237ae2b 100644
--- a/lib/ruby/stdlib/rubygems/user_interaction.rb
+++ b/lib/ruby/stdlib/rubygems/user_interaction.rb
@@ -6,6 +6,7 @@
 #++
 
 require 'rubygems/util'
+require 'rubygems/text'
 
 begin
   require 'io/console'
@@ -18,6 +19,8 @@ end
 
 module Gem::DefaultUserInteraction
 
+  include Gem::Text
+
   ##
   # The default UI is a class variable of the singleton class for this
   # module.
@@ -165,8 +168,8 @@ module Gem::UserInteraction
   # Calls +say+ with +msg+ or the results of the block if really_verbose
   # is true.
 
-  def verbose msg = nil
-    say(msg || yield) if Gem.configuration.really_verbose
+  def verbose(msg = nil)
+    say(clean_text(msg || yield)) if Gem.configuration.really_verbose
   end
 end
 
diff --git a/test/mri/rubygems/test_gem_installer.rb b/test/mri/rubygems/test_gem_installer.rb
index 129c235..8fe984e 100644
--- a/test/mri/rubygems/test_gem_installer.rb
+++ b/test/mri/rubygems/test_gem_installer.rb
@@ -1215,6 +1215,114 @@ gem 'other', version
     end
   end
 
+  def test_pre_install_checks_malicious_name_before_eval
+    spec = util_spec "malicious\n::Object.const_set(:FROM_EVAL, true)#", '1'
+    def spec.full_name # so the spec is buildable
+      "malicious-1"
+    end
+    def spec.validate(*args); end
+
+    util_build_gem spec
+
+    gem = File.join(@gemhome, 'cache', spec.file_name)
+
+    use_ui @ui do
+      @installer = Gem::Installer.at gem
+      e = assert_raises Gem::InstallError do
+        @installer.pre_install_checks
+      end
+      assert_equal "#<Gem::Specification name=malicious\n::Object.const_set(:FROM_EVAL, true)# version=1> has an invalid name", e.message
+    end
+    refute defined?(::Object::FROM_EVAL)
+  end
+
+  def test_pre_install_checks_malicious_require_paths_before_eval
+    spec = util_spec "malicious", '1'
+    def spec.full_name # so the spec is buildable
+      "malicious-1"
+    end
+    def spec.validate(*args); end
+    spec.require_paths = ["malicious\n``"]
+
+    util_build_gem spec
+
+    gem = File.join(@gemhome, 'cache', spec.file_name)
+
+    use_ui @ui do
+      @installer = Gem::Installer.at gem
+      e = assert_raises Gem::InstallError do
+        @installer.pre_install_checks
+      end
+      assert_equal "#<Gem::Specification name=malicious version=1> has an invalid require_paths", e.message
+    end
+  end
+
+  def test_pre_install_checks_malicious_extensions_before_eval
+    skip "mswin environment disallow to create file contained the carriage return code." if Gem.win_platform?
+
+    spec = util_spec "malicious", '1'
+    def spec.full_name # so the spec is buildable
+      "malicious-1"
+    end
+    def spec.validate(*args); end
+    spec.extensions = ["malicious\n``"]
+
+    util_build_gem spec
+
+    gem = File.join(@gemhome, 'cache', spec.file_name)
+
+    use_ui @ui do
+      @installer = Gem::Installer.at gem
+      e = assert_raises Gem::InstallError do
+        @installer.pre_install_checks
+      end
+      assert_equal "#<Gem::Specification name=malicious version=1> has an invalid extensions", e.message
+    end
+  end
+
+  def test_pre_install_checks_malicious_specification_version_before_eval
+    spec = util_spec "malicious", '1'
+    def spec.full_name # so the spec is buildable
+      "malicious-1"
+    end
+    def spec.validate(*args); end
+    spec.specification_version = "malicious\n``"
+
+    util_build_gem spec
+
+    gem = File.join(@gemhome, 'cache', spec.file_name)
+
+    use_ui @ui do
+      @installer = Gem::Installer.at gem
+      e = assert_raises Gem::InstallError do
+        @installer.pre_install_checks
+      end
+      assert_equal "#<Gem::Specification name=malicious version=1> has an invalid specification_version", e.message
+    end
+  end
+
+  def test_pre_install_checks_malicious_dependencies_before_eval
+    spec = util_spec "malicious", '1'
+    def spec.full_name # so the spec is buildable
+      "malicious-1"
+    end
+    def spec.validate(*args); end
+    spec.add_dependency "b\nfoo", '> 5'
+
+    util_build_gem spec
+
+    gem = File.join(@gemhome, 'cache', spec.file_name)
+
+    use_ui @ui do
+      @installer = Gem::Installer.at gem
+      @installer.ignore_dependencies = true
+      e = assert_raises Gem::InstallError do
+        @installer.pre_install_checks
+      end
+      assert_equal "#<Gem::Specification name=malicious version=1> has an invalid dependencies", e.message
+    end
+  end
+
   def test_shebang
     util_make_exec @spec, "#!/usr/bin/ruby"
 
diff --git a/test/mri/rubygems/test_gem_package.rb b/test/mri/rubygems/test_gem_package.rb
index 7848bc2..930825e 100644
--- a/test/mri/rubygems/test_gem_package.rb
+++ b/test/mri/rubygems/test_gem_package.rb
@@ -443,6 +443,42 @@ class TestGemPackage < Gem::Package::TarTestCase
                  "#{@destination} is not allowed", e.message)
   end
 
+  def test_extract_symlink_parent_doesnt_delete_user_dir
+    skip if RUBY_VERSION <= "1.8.7"
+
+    package = Gem::Package.new @gem
+
+    # Extract into a subdirectory of @destination; if this test fails it writes
+    # a file outside destination_subdir, but we want the file to remain inside
+    # @destination so it will be cleaned up.
+    destination_subdir = File.join @destination, 'subdir'
+    FileUtils.mkdir_p destination_subdir
+
+    destination_user_dir = File.join @destination, 'user'
+    destination_user_subdir = File.join destination_user_dir, 'dir'
+    FileUtils.mkdir_p destination_user_subdir
+
+    tgz_io = util_tar_gz do |tar|
+      tar.add_symlink 'link', destination_user_dir, 16877
+      tar.add_symlink 'link/dir', '.', 16877
+    end
+
+    e = assert_raises(Gem::Package::PathError, Errno::EACCES) do
+      package.extract_tar_gz tgz_io, destination_subdir
+    end
+
+    assert_path_exists destination_user_subdir
+
+    if Gem::Package::PathError === e
+      assert_equal("installing into parent path #{destination_user_subdir} of " +
+                  "#{destination_subdir} is not allowed", e.message)
+    elsif win_platform?
+      skip "symlink - must be admin with no UAC on Windows"
+    else
+      raise e
+    end
+  end
+
   def test_extract_tar_gz_directory
     package = Gem::Package.new @gem
 
diff --git a/test/mri/rubygems/test_gem_text.rb b/test/mri/rubygems/test_gem_text.rb
index 855e9ac..db96a20 100644
--- a/test/mri/rubygems/test_gem_text.rb
+++ b/test/mri/rubygems/test_gem_text.rb
@@ -74,4 +74,9 @@ Without the wrapping, the text might not look good in the RSS feed.
     assert_equal 7, levenshtein_distance("xxxxxxx", "ZenTest")
     assert_equal 7, levenshtein_distance("zentest", "xxxxxxx")
   end
+
+  def test_clean_text
+    assert_equal ".]2;nyan.", clean_text("\e]2;nyan\a")
+  end
+
 end