diff --git a/lib/tmpdir.rb b/lib/tmpdir.rb index f78fd72..6558a7e 100644 --- a/lib/tmpdir.rb +++ b/lib/tmpdir.rb @@ -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 + # 1, true, or yes 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 @@ -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 @@ -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? diff --git a/test/test_tmpdir.rb b/test/test_tmpdir.rb index c91fc33..a0b9c42 100644 --- a/test/test_tmpdir.rb +++ b/test/test_tmpdir.rb @@ -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]