minitar 0.6.1 → 0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6f701b9b323414ca305fd29f288088f7b6656342
4
- data.tar.gz: f0875b48ec987190fdd2a8c6e6d5785b87958af7
2
+ SHA256:
3
+ metadata.gz: fe4dcb6889d7dd3e767dc3329f8e874d4359577f219b0a76df859617a253c4fb
4
+ data.tar.gz: 345a765b717201d0a47dbb4d52572d68d51797fb41f7df0f314e9a43b160e6de
5
5
  SHA512:
6
- metadata.gz: b51ac6a3c8c9494f6d17392e6e7969836d2fae501e47f6083cbf0b3646dea93eb4838a25986b3723c071c791ac0ac770e8229276a77a5c06021398d82cbc4a0c
7
- data.tar.gz: 9423c08b9f8426d415b3f77647c53cf744180dbdc328f4f40e15a9632171696810902a341fa654133f3e4c8ca6b84277dd93892426bb553ed309785f47114228
6
+ metadata.gz: faf3628dd94d2a9f85c86a7662b8699911d022d5b7e75ea2f41effd6fd896252721e953203796cd99739f7c9e756066250d78e613ee606271d79c36753836df2
7
+ data.tar.gz: b0311f7166e41805adce3709c53a0acda7dcfe5b732abb16d278706b5e54b56cb2477e3e51bc0bf8d5f6143e61421fd11860a3e1fca22c57ea405f751fa8b369
@@ -78,6 +78,10 @@ Thanks to everyone who has contributed to minitar:
78
78
  * Mike Furr
79
79
  * Pete Fritchman
80
80
  * Zach Dennis
81
+ * ooooooo\_q
82
+ * Kazuyoshi Kato
83
+ * dearblue
84
+ * Kevin McDermott
81
85
 
82
86
  [Minitest]: https://github.com/seattlerb/minitest
83
87
  [quality commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
data/History.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## 0.7 / 2018-02-19
2
+
3
+ * Fixed issue [#28][] with a modified version of PR [#29][] covering the
4
+ security policy and position for Minitar. Thanks so much to ooooooo\_q for
5
+ the report and an initial patch. Additional information was added as
6
+ [#30][].
7
+
8
+ * dearblue contributed PR [#33][] providing a fix for Minitar::Reader when
9
+ the IO-like object does not have a `#pos` method.
10
+
11
+ * Kevin McDermott contributed PR [#34][] so that an InvalidTarStream is
12
+ raised if the tar header is not valid, preventing incorrect streaming of
13
+ files from a non-tarfile. This is a minor breaking change, so the version
14
+ has been bumped accordingly.
15
+
16
+ * Kazuyoshi Kato contributed PR [#26][] providing support for the GNU tar
17
+ long filename extension.
18
+
19
+ * Addressed a potential DOS with negative size fields in tar headers
20
+ ([#31][]). This has been handled in two ways: the size field in a tar
21
+ header is interpreted as a strict octal value and the Minitar reader will
22
+ raise an InvalidTarStream if the size ends up being negative anyway.
23
+
1
24
  ## 0.6.1 / 2017-02-07
2
25
 
3
26
  * Fixed issue [#24][] where streams were being improperly closed immediately
@@ -115,3 +138,8 @@
115
138
  [#16]: https://github.com/halostatue/minitar/issues/16
116
139
  [#23]: https://github.com/halostatue/minitar/issues/23
117
140
  [#24]: https://github.com/halostatue/minitar/issues/24
141
+ [#26]: https://github.com/halostatue/minitar/issues/26
142
+ [#28]: https://github.com/halostatue/minitar/issues/28
143
+ [#29]: https://github.com/halostatue/minitar/issues/29
144
+ [#30]: https://github.com/halostatue/minitar/issues/30
145
+ [#33]: https://github.com/halostatue/minitar/issues/33
@@ -14,9 +14,12 @@ coveralls :: {<img src="https://coveralls.io/repos/halostatue/minitar/badge.svg"
14
14
  The minitar library is a pure-Ruby library that provides the ability to deal
15
15
  with POSIX tar(1) archive files.
16
16
 
17
- This is release 0.6, providing a number of bug fixes including a directory
18
- traversal vulnerability, CVE-2016-10173. This release starts the migration and
19
- modernization of the code:
17
+ This is release 0.7, providing fixes for several issues and clarifying the
18
+ Minitar security stance. There are two minor breaking changes in this version
19
+ so that exceptions will be thrown if a negative size is provided in a tar
20
+ stream header or if the tar stream header is otherwise invalid.
21
+
22
+ This release continues the migration and modernization of the code:
20
23
 
21
24
  * the licence has been changed to match the modern Ruby licensing scheme
22
25
  (Ruby and Simplified BSD instead of Ruby and GNU GPL);
@@ -73,6 +76,34 @@ wrapped data stream object.
73
76
  tar.close
74
77
  end
75
78
 
79
+ == Minitar and Security
80
+
81
+ Minitar aims to be secure by default for the data *inside* of a tarfile. If
82
+ there are any security issues discovered, please feel free to open an issue.
83
+ Should you wish to make a more confidential report, you can find my PGP key
84
+ information at {Keybase}[https://keybase.io/halostatue]. Bear with me: I do not
85
+ use PGP regularly, so it may take some time to remember the command invocations
86
+ required to successfully handle this.
87
+
88
+ Minitar does *not* perform validation of path names provided to the convenience
89
+ calsses Minitar::Output and Minitar::Input, which use Kernel.open for their
90
+ underlying implementations when not given an IO-like object.
91
+
92
+ Improper use of these classes with arbitrary input filenames may leave your
93
+ your software to the same class of vulnerability as reported for Net::FTP
94
+ ({CVE-2017-17405}[https://nvd.nist.gov/vuln/detail/CVE-2017-17405]). Of
95
+ particular note, "if the localfile argument starts with the '|' pipe character,
96
+ the command following the pipe character is executed."
97
+
98
+ Additionally, the use of the `open-uri` library (which extends Kernel.open with
99
+ transparent implementations of Net::HTTP, Net::HTTPS, and Net::FTP), there are
100
+ other possible vulnerabilities when accepting arbitrary input, as
101
+ {detailed}[https://sakurity.com/blog/2015/02/28/openuri.html] by Egor Homakov.
102
+
103
+ These security vulnerabilities may be avoided, even with the Minitar::Output
104
+ and Minitar::Input convenience classes, by providing IO-like objects instead of
105
+ pathname-like objects as the source or destination of these classes.
106
+
76
107
  == minitar Semantic Versioning
77
108
 
78
109
  The minitar library uses a {Semantic Versioning}[http://semver.org/] scheme
@@ -73,7 +73,7 @@ end
73
73
  # tar.close
74
74
  # end
75
75
  module Archive::Tar::Minitar
76
- VERSION = '0.6.1'.freeze # :nodoc:
76
+ VERSION = '0.7'.freeze # :nodoc:
77
77
 
78
78
  # The base class for any minitar error.
79
79
  Error = Class.new(::StandardError)
@@ -91,6 +91,8 @@ module Archive::Tar::Minitar
91
91
  # The exception raised when a file contains a relative path in secure mode
92
92
  # (the default for this version).
93
93
  SecureRelativePathError = Class.new(Error)
94
+ # The exception raised when a file contains an invalid Posix header.
95
+ InvalidTarStream = Class.new(Error)
94
96
 
95
97
  class << self
96
98
  # Tests if +path+ refers to a directory. Fixes an apparently
@@ -36,6 +36,9 @@ module Archive::Tar::Minitar; end
36
36
  # unrecognized typeflag value as a regular file."
37
37
  class Archive::Tar::Minitar::PosixHeader
38
38
  BLOCK_SIZE = 512
39
+ MAGIC_BYTES = 'ustar'.freeze
40
+
41
+ GNU_EXT_LONG_LINK = '././@LongLink'
39
42
 
40
43
  # Fields that must be set in a POSIX tar(1) header.
41
44
  REQUIRED_FIELDS = [ :name, :size, :prefix, :mode ].freeze
@@ -81,7 +84,7 @@ class Archive::Tar::Minitar::PosixHeader
81
84
  mode = fields.shift.oct
82
85
  uid = fields.shift.oct
83
86
  gid = fields.shift.oct
84
- size = fields.shift.oct
87
+ size = strict_oct(fields.shift)
85
88
  mtime = fields.shift.oct
86
89
  checksum = fields.shift.oct
87
90
  typeflag = fields.shift
@@ -116,6 +119,13 @@ class Archive::Tar::Minitar::PosixHeader
116
119
  :linkname => linkname
117
120
  )
118
121
  end
122
+
123
+ private
124
+
125
+ def strict_oct(string)
126
+ return string.oct if string =~ /\A[0-7]*\z/
127
+ raise ArgumentError, "#{string.inspect} is not a valid octal string"
128
+ end
119
129
  end
120
130
 
121
131
  # Creates a new PosixHeader. A PosixHeader cannot be created unless
@@ -128,7 +138,7 @@ class Archive::Tar::Minitar::PosixHeader
128
138
  v[:mtime] = v[:mtime].to_i
129
139
  v[:checksum] ||= ''
130
140
  v[:typeflag] ||= '0'
131
- v[:magic] ||= 'ustar'
141
+ v[:magic] ||= MAGIC_BYTES
132
142
  v[:version] ||= '00'
133
143
 
134
144
  FIELDS.each do |f|
@@ -143,10 +153,15 @@ class Archive::Tar::Minitar::PosixHeader
143
153
  @empty
144
154
  end
145
155
 
156
+ # Indicates if the header has a valid magic value.
157
+ def valid?
158
+ empty? || @magic == MAGIC_BYTES
159
+ end
160
+
146
161
  # Returns +true+ if the header is a long name special header which indicates
147
162
  # that the next block of data is the filename.
148
163
  def long_name?
149
- typeflag == 'L' && name == '././@LongLink'
164
+ typeflag == 'L' && name == GNU_EXT_LONG_LINK
150
165
  end
151
166
 
152
167
  # A string representation of the header.
@@ -5,6 +5,7 @@ module Archive::Tar::Minitar
5
5
  # stream may be sequential or random access, but certain features only work
6
6
  # with random access data streams.
7
7
  class Reader
8
+ include Enumerable
8
9
  # This marks the EntryStream closed for reading without closing the
9
10
  # actual data stream.
10
11
  module InvalidEntryStream
@@ -179,7 +180,7 @@ module Archive::Tar::Minitar
179
180
  # Creates and returns a new Reader object.
180
181
  def initialize(io)
181
182
  @io = io
182
- @init_pos = io.pos
183
+ @init_pos = io.pos rescue nil
183
184
  end
184
185
 
185
186
  # Resets the read pointer to the beginning of data stream. Do not call
@@ -207,8 +208,11 @@ module Archive::Tar::Minitar
207
208
  return if @io.eof?
208
209
 
209
210
  header = Archive::Tar::Minitar::PosixHeader.from_stream(@io)
211
+ raise Archive::Tar::Minitar::InvalidTarStream unless header.valid?
210
212
  return if header.empty?
211
213
 
214
+ raise Archive::Tar::Minitar::InvalidTarStream if header.size < 0
215
+
212
216
  if header.long_name?
213
217
  name = @io.read(512).rstrip
214
218
  header = PosixHeader.from_stream(@io)
@@ -132,11 +132,8 @@ module Archive::Tar::Minitar
132
132
  # end
133
133
  def add_file_simple(name, opts = {}) # :yields BoundedWriteStream:
134
134
  raise ClosedStream if @closed
135
- name, prefix = split_name(name)
136
135
 
137
136
  header = {
138
- :prefix => prefix,
139
- :name => name,
140
137
  :mode => opts.fetch(:mode, 0o644),
141
138
  :mtime => opts.fetch(:mtime, nil),
142
139
  :gid => opts.fetch(:gid, nil),
@@ -161,7 +158,7 @@ module Archive::Tar::Minitar
161
158
 
162
159
  header[:size] = size
163
160
 
164
- @io.write(PosixHeader.new(header))
161
+ write_header(name, header)
165
162
 
166
163
  os = BoundedWriteStream.new(@io, opts[:size])
167
164
  if block_given?
@@ -208,8 +205,6 @@ module Archive::Tar::Minitar
208
205
  raise Archive::Tar::Minitar::NonSeekableStream
209
206
  end
210
207
 
211
- name, prefix = split_name(name)
212
-
213
208
  init_pos = @io.pos
214
209
  @io.write("\0" * 512) # placeholder for the header
215
210
 
@@ -222,15 +217,13 @@ module Archive::Tar::Minitar
222
217
  final_pos, @io.pos = @io.pos, init_pos
223
218
 
224
219
  header = {
225
- :name => name,
226
220
  :mode => opts[:mode],
227
221
  :mtime => opts[:mtime],
228
222
  :size => size,
229
223
  :gid => opts[:gid],
230
224
  :uid => opts[:uid],
231
- :prefix => prefix
232
225
  }
233
- @io.write(PosixHeader.new(header))
226
+ write_header(name, header)
234
227
  @io.pos = final_pos
235
228
  end
236
229
 
@@ -238,18 +231,15 @@ module Archive::Tar::Minitar
238
231
  def mkdir(name, opts = {})
239
232
  raise ClosedStream if @closed
240
233
 
241
- name, prefix = split_name(name)
242
234
  header = {
243
- :name => name,
244
235
  :mode => opts[:mode],
245
236
  :typeflag => '5',
246
237
  :size => 0,
247
238
  :gid => opts[:gid],
248
239
  :uid => opts[:uid],
249
240
  :mtime => opts[:mtime],
250
- :prefix => prefix
251
241
  }
252
- @io.write(PosixHeader.new(header))
242
+ write_header(name, header)
253
243
  nil
254
244
  end
255
245
 
@@ -275,11 +265,27 @@ module Archive::Tar::Minitar
275
265
 
276
266
  private
277
267
 
278
- def split_name(name)
279
- # TODO: Enable long-filename write support.
268
+ def write_header(long_name, header)
269
+ short_name, prefix, needs_long_name = split_name(long_name)
270
+
271
+ if needs_long_name
272
+ long_name_header = {
273
+ :prefix => '',
274
+ :name => PosixHeader::GNU_EXT_LONG_LINK,
275
+ :typeflag => 'L',
276
+ :size => long_name.length,
277
+ :mode => 0,
278
+ }
279
+ @io.write(PosixHeader.new(long_name_header))
280
+ @io.write(long_name)
281
+ @io.write("\0" * (512 - (long_name.length % 512)))
282
+ end
280
283
 
281
- raise FileNameTooLong if name.size > 256
284
+ new_header = header.merge({ :name => short_name, :prefix => prefix })
285
+ @io.write(PosixHeader.new(new_header))
286
+ end
282
287
 
288
+ def split_name(name)
283
289
  if name.size <= 100
284
290
  prefix = ''
285
291
  else
@@ -297,11 +303,9 @@ module Archive::Tar::Minitar
297
303
  prefix = (parts + [nxt]).join('/')
298
304
 
299
305
  name = newname
300
-
301
- raise FileNameTooLong if name.size > 100 || prefix.size > 155
302
306
  end
303
307
 
304
- [ name, prefix ]
308
+ [ name, prefix, (name.size > 100 || prefix.size > 155) ]
305
309
  end
306
310
  end
307
311
  end
@@ -71,4 +71,19 @@ class TestTarHeader < Minitest::Test
71
71
  h = Archive::Tar::Minitar::PosixHeader.from_stream header
72
72
  assert_equal('a ', h.name)
73
73
  end
74
+
75
+ def test_valid_with_valid_header
76
+ header = tar_file_header('a' * 100, '', 0o12345, 10)
77
+ header = StringIO.new(header)
78
+ h = Archive::Tar::Minitar::PosixHeader.from_stream header
79
+
80
+ assert(h.valid?)
81
+ end
82
+
83
+ def test_valid_with_invalid_header
84
+ header = StringIO.new("testing")
85
+ h = Archive::Tar::Minitar::PosixHeader.from_stream header
86
+
87
+ refute(h.valid?)
88
+ end
74
89
  end
@@ -4,9 +4,11 @@ require 'minitar'
4
4
  require 'minitest_helper'
5
5
 
6
6
  class TestTarOutput < Minitest::Test
7
+ NAMES = ['a', 'b', 'c', 'd' * 200]
8
+
7
9
  def setup
8
10
  FileUtils.mkdir_p('data__')
9
- %w(a b c).each do |filename|
11
+ NAMES.each do |filename|
10
12
  name = File.join('data__', filename)
11
13
  File.open(name, 'wb') { |f|
12
14
  f.puts "#{name}: 123456789012345678901234567890"
@@ -30,7 +32,7 @@ class TestTarOutput < Minitest::Test
30
32
  def test_file_looks_good
31
33
  Minitar::Output.open(@tarfile) do |os|
32
34
  Dir.chdir('data__') do
33
- %w(a b c).each do |name|
35
+ NAMES.each do |name|
34
36
  stat = File.stat(name)
35
37
  opts = { :size => stat.size, :mode => 0o644 }
36
38
  os.tar.add_file_simple(name, opts) do |ss|
@@ -41,19 +43,10 @@ class TestTarOutput < Minitest::Test
41
43
  end
42
44
  ff = File.open(@tarfile, 'rb')
43
45
  Minitar::Reader.open(ff) do |is|
44
- ii = 0
45
- is.each do |entry|
46
- case ii
47
- when 0
48
- assert_equal('a', entry.name)
49
- when 1
50
- assert_equal('b', entry.name)
51
- when 2
52
- assert_equal('c', entry.name)
53
- end
54
- ii += 1
46
+ names_from_tar = is.map do |entry|
47
+ entry.name
55
48
  end
56
- assert_equal(3, ii)
49
+ assert_equal(NAMES, names_from_tar)
57
50
  end
58
51
  ensure
59
52
  ff.close if ff
@@ -158,4 +158,14 @@ class TestTarReader < Minitest::Test
158
158
  end
159
159
  end
160
160
  end
161
+
162
+ def test_read_invalid_tar_file
163
+ assert_raises Archive::Tar::Minitar::InvalidTarStream do
164
+ Minitar::Reader.open(StringIO.new("testing")) do |r|
165
+ r.each_entry do |entry|
166
+ fail "invalid tar file should not read files"
167
+ end
168
+ end
169
+ end
170
+ end
161
171
  end
@@ -106,21 +106,24 @@ class TestTarWriter < Minitest::Test
106
106
  @dummyos.data[2 * i * 512, 512]
107
107
  )
108
108
  end
109
- assert_raises(Minitar::FileNameTooLong) do
110
- @os.add_file_simple(File.join('a' * 152, 'b' * 10, 'a' * 92),
111
- :mode => 0o644, :size => 10) {}
112
- end
113
- assert_raises(Minitar::FileNameTooLong) do
114
- @os.add_file_simple(File.join('a' * 162, 'b' * 10),
115
- :mode => 0o644, :size => 10) {}
116
- end
117
- assert_raises(Minitar::FileNameTooLong) do
118
- @os.add_file_simple(File.join('a' * 10, 'b' * 110),
119
- :mode => 0o644, :size => 10) {}
120
- end
109
+ end
110
+
111
+ def test_file_name_is_long
112
+ @dummyos.reset
113
+
114
+ @os.add_file_simple(File.join('a' * 152, 'b' * 10, 'c' * 92),
115
+ :mode => 0o644, :size => 10) {}
116
+ @os.add_file_simple(File.join('d' * 162, 'e' * 10),
117
+ :mode => 0o644, :size => 10) {}
118
+ @os.add_file_simple(File.join('f' * 10, 'g' * 110),
119
+ :mode => 0o644, :size => 10) {}
121
120
  # Issue #6.
122
- assert_raises(Minitar::FileNameTooLong) do
123
- @os.add_file_simple('a' * 114, :mode => 0o644, :size => 10) {}
121
+ @os.add_file_simple('a' * 114, :mode => 0o644, :size => 10) {}
122
+
123
+ # "././@LongLink", a file name, its actual header, its data, ...
124
+ 4.times do |i|
125
+ assert_equal(Minitar::PosixHeader::GNU_EXT_LONG_LINK,
126
+ @dummyos.data[4 * i * 512, 32].rstrip)
124
127
  end
125
128
  end
126
129
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minitar
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: '0.7'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Austin Ziegler
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-08 00:00:00.000000000 Z
11
+ date: 2018-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '5.10'
19
+ version: '5.11'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '5.10'
26
+ version: '5.11'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: hoe-doofus
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -154,21 +154,24 @@ dependencies:
154
154
  requirements:
155
155
  - - "~>"
156
156
  - !ruby/object:Gem::Version
157
- version: '3.16'
157
+ version: '3.17'
158
158
  type: :development
159
159
  prerelease: false
160
160
  version_requirements: !ruby/object:Gem::Requirement
161
161
  requirements:
162
162
  - - "~>"
163
163
  - !ruby/object:Gem::Version
164
- version: '3.16'
164
+ version: '3.17'
165
165
  description: |-
166
166
  The minitar library is a pure-Ruby library that provides the ability to deal
167
167
  with POSIX tar(1) archive files.
168
168
 
169
- This is release 0.6, providing a number of bug fixes including a directory
170
- traversal vulnerability, CVE-2016-10173. This release starts the migration and
171
- modernization of the code:
169
+ This is release 0.7, providing fixes for several issues and clarifying the
170
+ Minitar security stance. There are two minor breaking changes in this version
171
+ so that exceptions will be thrown if a negative size is provided in a tar
172
+ stream header or if the tar stream header is otherwise invalid.
173
+
174
+ This release continues the migration and modernization of the code:
172
175
 
173
176
  * the licence has been changed to match the modern Ruby licensing scheme
174
177
  (Ruby and Simplified BSD instead of Ruby and GNU GPL);
@@ -248,7 +251,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
248
251
  version: '0'
249
252
  requirements: []
250
253
  rubyforge_project:
251
- rubygems_version: 2.5.1
254
+ rubygems_version: 2.7.7
252
255
  signing_key:
253
256
  specification_version: 4
254
257
  summary: The minitar library is a pure-Ruby library that provides the ability to deal