Skip to content

Commit

Permalink
Resolve relative path vulnerability
Browse files Browse the repository at this point in the history
Fixes #16, CVE-2016-10173

Also makes the move from minitar.gemspec to archive-tar-minitar.gemspec.
  • Loading branch information
halostatue committed Feb 7, 2017
1 parent 27569df commit 30e6268
Show file tree
Hide file tree
Showing 14 changed files with 197 additions and 66 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.md
*.swp
*~
.rake_tasks~
Expand All @@ -10,7 +11,6 @@ doc
html
pkg
publish
ri
test/cache.tst
tmp/
.byebug_history
2 changes: 2 additions & 0 deletions .hoerc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ exclude: !ruby/regexp '/
|
[gG]emfile(?:\.lock)?
|
support\/hoe\/
|
\.gemspec$
|
Vagrantfile
Expand Down
15 changes: 5 additions & 10 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
---
language: ruby
rvm:
- 2.3.1
- 2.2.3
- 2.1.6
- 2.4.0
- 2.3.3
- 2.2.6
- 2.1.9
- 2.0.0
- jruby-9.0.5.0
- jruby-9.1.5.0
- jruby-9.1.6.0
- 1.9.3
- 1.8.7
- ree
Expand All @@ -20,12 +21,6 @@ matrix:
gemfile:
- Gemfile
before_script:
- |
case "${TRAVIS_RUBY_VERSION}" in
rbx*)
gem install psych
;;
esac
- rake travis:before -t
script: rake travis
after_script:
Expand Down
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# -*- ruby -*-

# NOTE: This file is present to keep Travis CI happy. Edits to it will not
# be accepted, except to remove all of this crap later.
# be accepted.

source 'https://rubygems.org/'

mime_version =
if RUBY_VERSION < '1.9'
gem 'rdoc', '< 4.0'
# gem 'ruby-debug'
gem 'rake', '~> 10.0'
'1.25'
elsif RUBY_VERSION < '2.0'
# gem 'debugger' if RUBY_ENGINE == 'ruby'
Expand Down
37 changes: 25 additions & 12 deletions History.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
`archive-tar-minitar` will install both `minitar` and `minitar-cli`, at
least until version 1.0.)

* Minitar extraction before 0.6 traverses directories if the tarball
includes a relative directory reference, as reported in [#16][] by
@ecneladis. This has been disallowed entirely and will throw a
SecureRelativePathError when found. Additionally, if the final
destination of an entry is an already-existing symbolic link, the
existing symbolic link will be removed and the file will be written
correctly (on platforms that support symblic links).

* Enhancements:

* Licence change. After speaking with Mauricio Fernández, we have changed
Expand Down Expand Up @@ -51,18 +59,16 @@

* Bugs:

* Fix [#2](https://github.com/halostatue/minitar/issues/2) to handle IO
streams that are not seekable, such as pipes, STDIN, or STDOUT.
* Fix [#3](https://github.com/halostatue/minitar/issues/3) to make the
test timezone resilient.
* Fix [#4](https://github.com/halostatue/minitar/issues/4) for supporting
the reading of tar files with filenames in the GNU long filename
extension format. Ported from @atoulme’s fork, originally provided by
Curtis Sampson.
* Fix [#6](https://github.com/halostatue/minitar/issues/6) by making it
raise the correct error for a long filename with no path components.
* Fix [#14](https://github.com/halostatue/minitar/pull/6) provided by
@kzys should fix Windows detection issues.
* Fix [#2][] to handle IO streams that are not seekable, such as pipes,
STDIN, or STDOUT.
* Fix [#3][] to make the test timezone resilient.
* Fix [#4][] for supporting the reading of tar files with filenames in
the GNU long filename extension format. Ported from @atoulme’s fork,
originally provided by Curtis Sampson.
* Fix [#6][] by making it raise the correct error for a long filename
with no path components.
* Fix [#14][] provided by @kzys should fix Windows detection issues.
* Fix [#16][] as specified above.

* Development:

Expand All @@ -83,3 +89,10 @@

* Initial release. Does files and directories. Command does create, extract,
and list.

[#2]: https://github.com/halostatue/minitar/issues/2
[#3]: https://github.com/halostatue/minitar/issues/3
[#4]: https://github.com/halostatue/minitar/issues/4
[#6]: https://github.com/halostatue/minitar/issues/6
[#14]: https://github.com/halostatue/minitar/issues/14
[#16]: https://github.com/halostatue/minitar/issues/16
3 changes: 1 addition & 2 deletions Licence.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
minitar is free software that may be redistributed and/or modified under the
terms of Ruby’s licence or the Simplified BSD licence.

* Copyright 2004–2014 Austin Ziegler.
* Copyright 2004–2017 Austin Ziegler.
* Portions copyright 2004 Mauricio Julio Fernández Pradier.
* Portions copyright 2001–2004 Satoru Takabayashi.

### Simplified BSD Licence

Expand Down
7 changes: 3 additions & 4 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ home :: https://github.com/halostatue/minitar/
code :: https://github.com/halostatue/minitar/
bugs :: https://github.com/halostatue/minitar/issues
rdoc :: http://rdoc.info/gems/minitar/
continuous integration :: {<img src="https://travis-ci.org/halostatue/minitar.png" />}[https://travis-ci.org/halostatue/minitar]
continuous integration :: {<img src="https://travis-ci.org/halostatue/minitar.svg" />}[https://travis-ci.org/halostatue/minitar]
{<img src="https://ci.appveyor.com/api/projects/status/bj4gqn3gp3gu45sa?svg=true" />}[https://ci.appveyor.com/project/halostatue/minitar]

test coverage :: {<img src="https://coveralls.io/repos/halostatue/minitar/badge.png" alt="Coverage Status" />}[https://coveralls.io/r/halostatue/minitar]
test coverage :: {<img src="https://coveralls.io/repos/halostatue/minitar/badge.svg" alt="Coverage Status" />}[https://coveralls.io/r/halostatue/minitar]

== Description

Expand Down Expand Up @@ -49,7 +48,7 @@ Minitar::Output#close automatically closes both the Output object and the
wrapped data stream object.

begin
sgz = Zlib::GzipWriter.new(StringIO.new(""))
sgz = Zlib::GzipWriter.new(StringIO.new(String.new))
tar = Output.new(sgz)
Find.find('tests') do |entry|
Minitar.pack_file(entry, tar)
Expand Down
9 changes: 6 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ require 'rubygems'
require 'hoe'
require 'rake/clean'

$LOAD_PATH.unshift('support')

Hoe.plugin :doofus
Hoe.plugin :gemspec2
Hoe.plugin :git
Hoe.plugin :minitest
Hoe.plugin :travis
Hoe.plugin :deprecated_gem
Hoe.plugin :email unless ENV['CI'] or ENV['TRAVIS']

spec = Hoe.spec 'minitar' do
developer('Austin Ziegler', 'halostatue@gmail.com')

self.require_ruby_version '>= 1.8'
require_ruby_version '>= 1.8'

self.history_file = 'History.md'
self.readme_file = 'README.rdoc'
Expand All @@ -26,8 +29,8 @@ spec = Hoe.spec 'minitar' do
extra_dev_deps << ['hoe-rubygems', '~> 1.0']
extra_dev_deps << ['hoe-travis', '~> 1.2']
extra_dev_deps << ['minitest', '~> 5.3']
extra_dev_deps << ['minitest-autotest', ['>= 1.0.b', '<2']]
extra_dev_deps << ['rake', '~> 10.0']
extra_dev_deps << ['minitest-autotest', ['>= 1.0', '<2']]
extra_dev_deps << ['rake', '>= 10.0', '< 12']
extra_dev_deps << ['rdoc', '>= 0.0']
end

Expand Down
45 changes: 33 additions & 12 deletions archive-tar-minitar.gemspec
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
# -*- encoding: utf-8 -*-
# stub: archive-tar-minitar 0.6 ruby lib

minitar = Gem::Specification.load('minitar.gemspec')
minitar.name = 'archive-tar-minitar'
minitar.description =
minitar.summary = %q(This gem is deprecated. Just install 'minitar'.)
minitar.files.delete_if { |f| f !~ %r{lib/archive-tar-minitar\.rb} }
minitar.extra_rdoc_files.clear
minitar.rdoc_options.clear
minitar.dependencies.clear
minitar.add_dependency(%q<minitar>, "~> #{minitar.version}")
minitar.add_dependency(%q<minitar-cli>, "<= 1.0")

minitar
Gem::Specification.new do |s|
s.name = "archive-tar-minitar"
s.version = "0.6"

s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.require_paths = ["lib"]
s.authors = ["Austin Ziegler"]
s.date = "2017-02-06"
s.description = "'archive-tar-minitar' has been deprecated; just install 'minitar'. The minitar library is a pure-Ruby library that provides the ability to deal\nwith POSIX tar(1) archive files.\n\nThis is release 0.6, \u{2026}\n\nminitar (previously called Archive::Tar::Minitar) is based heavily on code\noriginally written by Mauricio Julio Fern\u{e1}ndez Pradier for the rpa-base\nproject."
s.email = ["halostatue@gmail.com"]
s.files = ["lib/archive-tar-minitar.rb"]
s.homepage = "https://github.com/halostatue/minitar/"
s.licenses = ["Ruby", "BSD-2-Clause"]
s.post_install_message = "'archive-tar-minitar' has been deprecated; just install 'minitar'."
s.required_ruby_version = Gem::Requirement.new(">= 1.8")
s.rubygems_version = "2.5.1"
s.summary = "'archive-tar-minitar' has been deprecated; just install 'minitar'."

if s.respond_to? :specification_version then
s.specification_version = 4

if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<minitar>, ["~> 0.6"])
s.add_runtime_dependency(%q<minitar-cli>, ["<= 1.0"])
else
s.add_dependency(%q<minitar>, ["~> 0.6"])
s.add_dependency(%q<minitar-cli>, ["<= 1.0"])
end
else
s.add_dependency(%q<minitar>, ["~> 0.6"])
s.add_dependency(%q<minitar-cli>, ["<= 1.0"])
end
end
11 changes: 8 additions & 3 deletions lib/archive/tar/minitar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,22 @@ def modules
module Archive::Tar::Minitar
VERSION = '0.6' # :nodoc:

# The base class for any minitar error.
Error = Class.new(StandardError)
# Raised when a wrapped data stream class is not seekable.
NonSeekableStream = Class.new(StandardError)
NonSeekableStream = Class.new(Error)
# The exception raised when operations are performed on a stream that has
# previously been closed.
ClosedStream = Class.new(StandardError)
ClosedStream = Class.new(Error)
# The exception raised when a filename exceeds 256 bytes in length, the
# maximum supported by the standard Tar format.
FileNameTooLong = Class.new(StandardError)
FileNameTooLong = Class.new(Error)
# The exception raised when a data stream ends before the amount of data
# expected in the archive's PosixHeader.
UnexpectedEOF = Class.new(StandardError)
# The exception raised when a file contains a relative path in secure mode
# (the default for this version).
SecureRelativePathError = Class.new(Error)

class << self
# Tests if +path+ refers to a directory. Fixes an apparently
Expand Down
34 changes: 27 additions & 7 deletions lib/archive/tar/minitar/input.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,25 @@ def extract_entry(destdir, entry) # :yields action, name, stats:
:entry => entry
}

# extract_entry is not vulnerable to prefix '/' vulnerabilities, but it
# is vulnerable to relative path directories. This code will break this
# vulnerability. For this version, we are breaking relative paths HARD by
# throwing an exception.
#
# Future versions may permit relative paths as long as the file does not
# leave +destdir+.
#
# However, squeeze consecutive '/' characters together.
full_name = entry.full_name.squeeze('/')

if full_name =~ /\.{2}(?:\/|\z)/
raise SecureRelativePathError, %q(Path contains '..')
end

if entry.directory?
dest = File.join(destdir, entry.full_name)
dest = File.join(destdir, full_name)

yield :dir, entry.full_name, stats if block_given?
yield :dir, full_name, stats if block_given?

if Archive::Tar::Minitar.dir?(dest)
begin
Expand All @@ -109,6 +124,8 @@ def extract_entry(destdir, entry) # :yields action, name, stats:
nil
end
else
File.unlink(dest.chomp('/')) if File.symlink?(dest.chomp('/'))

FileUtils.mkdir_p(dest, :mode => entry.mode)
FileUtils.chmod(entry.mode, dest)
end
Expand All @@ -117,13 +134,16 @@ def extract_entry(destdir, entry) # :yields action, name, stats:
fsync_dir(File.join(dest, ".."))
return
else # it's a file
destdir = File.join(destdir, File.dirname(entry.full_name))
destdir = File.join(destdir, File.dirname(full_name))
FileUtils.mkdir_p(destdir, :mode => 0755)

destfile = File.join(destdir, File.basename(entry.full_name))
destfile = File.join(destdir, File.basename(full_name))

File.unlink(destfile) if File.symlink?(destfile)

FileUtils.chmod(0600, destfile) rescue nil # Errno::ENOENT

yield :file_start, entry.full_name, stats if block_given?
yield :file_start, full_name, stats if block_given?

File.open(destfile, "wb", entry.mode) do |os|
loop do
Expand All @@ -133,7 +153,7 @@ def extract_entry(destdir, entry) # :yields action, name, stats:
stats[:currinc] = os.write(data)
stats[:current] += stats[:currinc]

yield :file_progress, entry.full_name, stats if block_given?
yield :file_progress, full_name, stats if block_given?
end
os.fsync
end
Expand All @@ -142,7 +162,7 @@ def extract_entry(destdir, entry) # :yields action, name, stats:
fsync_dir(File.dirname(destfile))
fsync_dir(File.join(File.dirname(destfile), ".."))

yield :file_done, entry.full_name, stats if block_given?
yield :file_done, full_name, stats if block_given?
end
end

Expand Down
20 changes: 10 additions & 10 deletions minitar.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Gem::Specification.new do |s|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.require_paths = ["lib"]
s.authors = ["Austin Ziegler"]
s.date = "2016-11-08"
s.date = "2017-02-06"
s.description = "The minitar library is a pure-Ruby library that provides the ability to deal\nwith POSIX tar(1) archive files.\n\nThis is release 0.6, \u{2026}\n\nminitar (previously called Archive::Tar::Minitar) is based heavily on code\noriginally written by Mauricio Julio Fern\u{e1}ndez Pradier for the rpa-base\nproject."
s.email = ["halostatue@gmail.com"]
s.extra_rdoc_files = ["Code-of-Conduct.md", "Contributing.md", "History.md", "Licence.md", "Manifest.txt", "README.rdoc", "docs/bsdl.txt", "docs/ruby.txt"]
Expand All @@ -30,21 +30,21 @@ Gem::Specification.new do |s|
s.add_development_dependency(%q<hoe-git>, ["~> 1.6"])
s.add_development_dependency(%q<hoe-rubygems>, ["~> 1.0"])
s.add_development_dependency(%q<hoe-travis>, ["~> 1.2"])
s.add_development_dependency(%q<minitest-autotest>, ["< 2", ">= 1.0.b"])
s.add_development_dependency(%q<rake>, ["~> 10.0"])
s.add_development_dependency(%q<minitest-autotest>, ["< 2", ">= 1.0"])
s.add_development_dependency(%q<rake>, ["< 12", ">= 10.0"])
s.add_development_dependency(%q<rdoc>, [">= 0.0"])
s.add_development_dependency(%q<hoe>, ["~> 3.15"])
s.add_development_dependency(%q<hoe>, ["~> 3.16"])
else
s.add_dependency(%q<minitest>, ["~> 5.9"])
s.add_dependency(%q<hoe-doofus>, ["~> 1.0"])
s.add_dependency(%q<hoe-gemspec2>, ["~> 1.1"])
s.add_dependency(%q<hoe-git>, ["~> 1.6"])
s.add_dependency(%q<hoe-rubygems>, ["~> 1.0"])
s.add_dependency(%q<hoe-travis>, ["~> 1.2"])
s.add_dependency(%q<minitest-autotest>, ["< 2", ">= 1.0.b"])
s.add_dependency(%q<rake>, ["~> 10.0"])
s.add_dependency(%q<minitest-autotest>, ["< 2", ">= 1.0"])
s.add_dependency(%q<rake>, ["< 12", ">= 10.0"])
s.add_dependency(%q<rdoc>, [">= 0.0"])
s.add_dependency(%q<hoe>, ["~> 3.15"])
s.add_dependency(%q<hoe>, ["~> 3.16"])
end
else
s.add_dependency(%q<minitest>, ["~> 5.9"])
Expand All @@ -53,9 +53,9 @@ Gem::Specification.new do |s|
s.add_dependency(%q<hoe-git>, ["~> 1.6"])
s.add_dependency(%q<hoe-rubygems>, ["~> 1.0"])
s.add_dependency(%q<hoe-travis>, ["~> 1.2"])
s.add_dependency(%q<minitest-autotest>, ["< 2", ">= 1.0.b"])
s.add_dependency(%q<rake>, ["~> 10.0"])
s.add_dependency(%q<minitest-autotest>, ["< 2", ">= 1.0"])
s.add_dependency(%q<rake>, ["< 12", ">= 10.0"])
s.add_dependency(%q<rdoc>, [">= 0.0"])
s.add_dependency(%q<hoe>, ["~> 3.15"])
s.add_dependency(%q<hoe>, ["~> 3.16"])
end
end
Loading

0 comments on commit 30e6268

Please # to comment.