Skip to content

Commit a9790d5

Browse files
committed
Fix 1 — Sticky bit check bypass (lines 48-58 and 128-132)
1 parent 474386d commit a9790d5

2 files changed

Lines changed: 60 additions & 1 deletion

File tree

lib/tmpdir.rb

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ class Dir
2222
# require 'tmpdir'
2323
# Dir.tmpdir # => "/tmp"
2424

25+
# Returns whether world-writable directories without the sticky bit
26+
# should be allowed as temporary directories.
27+
# Set +RUBY_TMPDIR_ALLOW_WORLD_WRITABLE+ environment variable to
28+
# <code>1</code>, <code>true</code>, or <code>yes</code> to allow.
29+
# This is useful in container environments (e.g. Kubernetes with
30+
# emptyDir volumes) where temporary directories are mounted with
31+
# mode 0777 without the sticky bit.
32+
def self.allow_world_writable?
33+
/\A(1|true|yes)\z/i.match?(ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"])
34+
end
35+
private_class_method :allow_world_writable?
36+
2537
def self.tmpdir
2638
Tmpname::TMPDIR_CANDIDATES.find do |name, dir|
2739
unless dir
@@ -37,6 +49,9 @@ def self.tmpdir
3749
# writable just from stat; OS mechanisms other than user/group/world bits can affect this.
3850
warn "#{name} is not writable: #{dir}"
3951
when stat.world_writable? && !stat.sticky?
52+
if allow_world_writable?
53+
break dir
54+
end
4055
warn "#{name} is world-writable: #{dir}"
4156
else
4257
break dir
@@ -104,7 +119,7 @@ def self.mktmpdir(prefix_suffix=nil, *rest, **options, &block)
104119
begin
105120
yield path.dup
106121
ensure
107-
unless base
122+
unless base or allow_world_writable?
108123
base = File.dirname(path)
109124
stat = File.stat(base)
110125
if stat.world_writable? and !stat.sticky?

test/test_tmpdir.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,50 @@ def test_world_writable
4747
end
4848
end
4949

50+
def test_world_writable_allowed_by_env
51+
omit "no meaning on this platform" if /mswin|mingw/ =~ RUBY_PLATFORM
52+
Dir.mktmpdir do |tmpdir|
53+
envs = %w[TMPDIR TMP TEMP]
54+
oldenv = envs.each_with_object({}) {|v, h| h[v] = ENV.delete(v)}
55+
old_allow = ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"]
56+
begin
57+
ENV[envs[0]] = tmpdir
58+
File.chmod(0777, tmpdir)
59+
60+
# Without env var, world-writable without sticky should be rejected
61+
ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"] = nil
62+
assert_not_equal(tmpdir, assert_warn(/world-writable/) {Dir.tmpdir})
63+
64+
# With env var set, world-writable without sticky should be accepted
65+
ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"] = "1"
66+
assert_equal(tmpdir, Dir.tmpdir)
67+
68+
# mktmpdir should also work
69+
newdir = Dir.mktmpdir("d", tmpdir) do |dir|
70+
assert_file.directory? dir
71+
assert_equal(tmpdir, File.dirname(dir))
72+
dir
73+
end
74+
assert_file.not_exist?(newdir)
75+
76+
# Other accepted values
77+
%w[true yes TRUE Yes].each do |val|
78+
ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"] = val
79+
assert_equal(tmpdir, Dir.tmpdir, "should accept with value #{val.inspect}")
80+
end
81+
82+
# Invalid values should not bypass
83+
%w[0 false no 2 enabled].each do |val|
84+
ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"] = val
85+
assert_not_equal(tmpdir, Dir.tmpdir, "should reject with value #{val.inspect}")
86+
end
87+
ensure
88+
ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"] = old_allow
89+
ENV.update(oldenv)
90+
end
91+
end
92+
end
93+
5094
def test_tmpdir_not_empty_parent
5195
Dir.mktmpdir do |tmpdir|
5296
envs = %w[TMPDIR TMP TEMP]

0 commit comments

Comments
 (0)