Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/rubygems/package.rb
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,11 @@ def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc:
directories << mkdir
end

real_mkdir = File.realpath(mkdir)
unless real_mkdir == destination_dir || normalize_path(real_mkdir).start_with?(normalize_path(destination_dir + "/"))
raise Gem::Package::PathError.new(real_mkdir, destination_dir)
end

if entry.file?
File.open(destination, "wb") do |out|
copy_stream(tar.io, out, entry.size)
Expand Down
26 changes: 26 additions & 0 deletions test/rubygems/test_gem_package.rb
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,32 @@ def test_extract_tar_gz_symlink_directory
File.read(extracted)
end

def test_extract_tar_gz_rejects_preexisting_symlink_escape
omit "Symlinks not supported or not enabled" unless symlink_supported?

package = Gem::Package.new @gem

tgz_io = util_tar_gz do |tar|
tar.add_file "lib/owned.txt", 0o644 do |io|
io.write "poc-content"
end
end

escape_dir = File.join(@tempdir, "escape")
FileUtils.mkdir_p escape_dir

FileUtils.rm_rf File.join(@destination, "lib")
File.symlink escape_dir, File.join(@destination, "lib")

escaped = File.join(escape_dir, "owned.txt")

assert_raise Gem::Package::PathError do
package.extract_tar_gz tgz_io, @destination
end

refute File.exist?(escaped), "must not write outside extraction root via symlink"
end

def test_extract_symlink_into_symlink_dir
omit "Symlinks not supported or not enabled" unless symlink_supported?
package = Gem::Package.new @gem
Expand Down
Loading