minitar 0.6.1 → 0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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