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
17 changes: 16 additions & 1 deletion lib/tmpdir.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ class Dir
# require 'tmpdir'
# Dir.tmpdir # => "/tmp"

# Returns whether world-writable directories without the sticky bit
# should be allowed as temporary directories.
# Set +RUBY_TMPDIR_ALLOW_WORLD_WRITABLE+ environment variable to
# <code>1</code>, <code>true</code>, or <code>yes</code> to allow.
# This is useful in container environments (e.g. Kubernetes with
# emptyDir volumes) where temporary directories are mounted with
# mode 0777 without the sticky bit.
def self.allow_world_writable?
/\A(1|true|yes)\z/i.match?(ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"])
end
private_class_method :allow_world_writable?

def self.tmpdir
Tmpname::TMPDIR_CANDIDATES.find do |name, dir|
unless dir
Expand All @@ -37,6 +49,9 @@ def self.tmpdir
# writable just from stat; OS mechanisms other than user/group/world bits can affect this.
warn "#{name} is not writable: #{dir}"
when stat.world_writable? && !stat.sticky?
if allow_world_writable?
break dir
end
warn "#{name} is world-writable: #{dir}"
else
break dir
Expand Down Expand Up @@ -104,7 +119,7 @@ def self.mktmpdir(prefix_suffix=nil, *rest, **options, &block)
begin
yield path.dup
ensure
unless base
unless base or allow_world_writable?
base = File.dirname(path)
stat = File.stat(base)
if stat.world_writable? and !stat.sticky?
Expand Down
44 changes: 44 additions & 0 deletions test/test_tmpdir.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,50 @@ def test_world_writable
end
end

def test_world_writable_allowed_by_env
omit "no meaning on this platform" if /mswin|mingw/ =~ RUBY_PLATFORM
Dir.mktmpdir do |tmpdir|
envs = %w[TMPDIR TMP TEMP]
oldenv = envs.each_with_object({}) {|v, h| h[v] = ENV.delete(v)}
old_allow = ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"]
begin
ENV[envs[0]] = tmpdir
File.chmod(0777, tmpdir)

# Without env var, world-writable without sticky should be rejected
ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"] = nil
assert_not_equal(tmpdir, assert_warn(/world-writable/) {Dir.tmpdir})

# With env var set, world-writable without sticky should be accepted
ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"] = "1"
assert_equal(tmpdir, Dir.tmpdir)

# mktmpdir should also work
newdir = Dir.mktmpdir("d", tmpdir) do |dir|
assert_file.directory? dir
assert_equal(tmpdir, File.dirname(dir))
dir
end
assert_file.not_exist?(newdir)

# Other accepted values
%w[true yes TRUE Yes].each do |val|
ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"] = val
assert_equal(tmpdir, Dir.tmpdir, "should accept with value #{val.inspect}")
end

# Invalid values should not bypass
%w[0 false no 2 enabled].each do |val|
ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"] = val
assert_not_equal(tmpdir, Dir.tmpdir, "should reject with value #{val.inspect}")
end
ensure
ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"] = old_allow
ENV.update(oldenv)
end
end
end

def test_tmpdir_not_empty_parent
Dir.mktmpdir do |tmpdir|
envs = %w[TMPDIR TMP TEMP]
Expand Down