[#92063] [Ruby trunk Misc#15723] Reconsider numbered parameters — zverok.offline@...
Issue #15723 has been updated by zverok (Victor Shepelev).
3 messages
2019/03/31
[ruby-core:91804] [Ruby trunk Bug#15555] Dir.mktmpdir checks permissions and raise ArgumentError after yielding to block (ensure) & leaks allocated tempdir
From:
nagachika00@...
Date:
2019-03-13 00:21:51 UTC
List:
ruby-core #91804
Issue #15555 has been updated by nagachika (Tomoyuki Chikanaga).
Backport changed from 2.4: DONE, 2.5: REQUIRED, 2.6: DONE to 2.4: DONE, 2.5: DONE, 2.6: DONE
ruby_2_5 r67241 merged revision(s) 66909.
----------------------------------------
Bug #15555: Dir.mktmpdir checks permissions and raise ArgumentError after yielding to block (ensure) & leaks allocated tempdir
https://bugs.ruby-lang.org/issues/15555#change-77078
* Author: kbogtob (Karim Bogtob)
* Status: Closed
* Priority: Normal
* Assignee:
* Target version:
* ruby -v: ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux-gnu]
* Backport: 2.4: DONE, 2.5: DONE, 2.6: DONE
----------------------------------------
The current implementation of the `Dir.mktmpdir` is the following:
```ruby
def Dir.mktmpdir(prefix_suffix=nil, *rest)
path = Tmpname.create(prefix_suffix || "d", *rest) {|n| mkdir(n, 0700)}
if block_given?
begin
yield path
ensure
stat = File.stat(File.dirname(path))
if stat.world_writable? and !stat.sticky?
raise ArgumentError, "parent directory is world writable but not sticky"
end
FileUtils.remove_entry path
end
else
path
end
end
```
When used explicitly with a directory, it raises an `ArgumentError` when the directory is world writable (`File::Stat#world_writable?`) but not sticky (`File::Stat#sticky?`). This is a normal behavior. But with how the method is written, the behavior is inconsistent and raises an `ArgumentError` only when using it with the block form. Worse, it raises the error in the ensure block instead of the first thing done in the method. So it raises the `ArgumentError` **after** it yields to the block, so after the job is already done.
Proof:
```ruby
irb(main):001:0> puts `ls -la /tmp`
total 4
drwxrwsrwx. 2 root 1000090000 6 Jan 22 10:02 .
drwxr-xr-x. 18 root root 4096 Jan 22 09:58 ..
=> nil
irb(main):002:0> # /tmp is non sticky but world writable
irb(main):003:0* Dir.mktmpdir(nil, '/tmp') do |tmpdir|
irb(main):004:1* puts "Hello, I'm using #{tmpdir}"
irb(main):005:1> end
Hello, I'm using /tmp/d20190122-26-5biuz2
ArgumentError: parent directory is world writable but not sticky
from /opt/rh/rh-ruby24/root/usr/share/ruby/tmpdir.rb:93:in `mktmpdir'
from (irb):3
from /opt/rh/rh-ruby24/root/usr/bin/irb:11:in `<top (required)>'
from /opt/rh/rh-ruby24/root/usr/local/share/gems/gems/bundler-1.15.4/lib/bundler/cli/exec.rb:74:in `load'
from /opt/rh/rh-ruby24/root/usr/local/share/gems/gems/bundler-1.15.4/lib/bundler/cli/exec.rb:74:in `kernel_load'
from /opt/rh/rh-ruby24/root/usr/local/share/gems/gems/bundler-1.15.4/lib/bundler/cli/exec.rb:27:in `run'
from /opt/rh/rh-ruby24/root/usr/local/share/gems/gems/bundler-1.15.4/lib/bundler/cli.rb:362:in `exec'
from /opt/rh/rh-ruby24/root/usr/local/share/gems/gems/bundler-1.15.4/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
from /opt/rh/rh-ruby24/root/usr/local/share/gems/gems/bundler-1.15.4/lib/bundler/vendor/thor/lib/thor/invocation.rb:126:in `invoke_command'
from /opt/rh/rh-ruby24/root/usr/local/share/gems/gems/bundler-1.15.4/lib/bundler/vendor/thor/lib/thor.rb:387:in `dispatch'
from /opt/rh/rh-ruby24/root/usr/local/share/gems/gems/bundler-1.15.4/lib/bundler/cli.rb:22:in `dispatch'
from /opt/rh/rh-ruby24/root/usr/local/share/gems/gems/bundler-1.15.4/lib/bundler/vendor/thor/lib/thor/base.rb:466:in `start'
from /opt/rh/rh-ruby24/root/usr/local/share/gems/gems/bundler-1.15.4/lib/bundler/cli.rb:13:in `start'
from /opt/rh/rh-ruby24/root/usr/local/share/gems/gems/bundler-1.15.4/exe/bundle:30:in `block in <top (required)>'
from /opt/rh/rh-ruby24/root/usr/local/share/gems/gems/bundler-1.15.4/lib/bundler/friendly_errors.rb:121:in `with_friendly_errors'
from /opt/rh/rh-ruby24/root/usr/local/share/gems/gems/bundler-1.15.4/exe/bundle:22:in `<top (required)>'
from /opt/rh/rh-ruby24/root/usr/bin/bundle:23:in `load'
from /opt/rh/rh-ruby24/root/usr/bin/bundle:23:in `<main>'
irb(main):006:0> puts `ls -la /tmp`
total 4
drwxrwsrwx. 3 root 1000090000 33 Jan 22 10:03 .
drwxr-xr-x. 18 root root 4096 Jan 22 09:58 ..
drwx--S---. 2 1000090000 1000090000 6 Jan 22 10:03 d20190122-26-5biuz2
=> nil
```
We can also see that the allocated directory **remains** after the exception is raised as the implementation raised in the ensure block.
An advisable implementation would be:
```ruby
def Dir.mktmpdir(prefix_suffix=nil, *rest)
path = Tmpname.create(prefix_suffix || "d", *rest) {|n| mkdir(n, 0700)}
stat = File.stat(File.dirname(path))
if stat.world_writable? and !stat.sticky?
FileUtils.remove_entry path
raise ArgumentError, "parent directory is world writable but not sticky"
end
if block_given?
begin
yield path
ensure
FileUtils.remove_entry path
end
else
path
end
end
```
This way, it will make the checks first and raise directly. And no temporary directory will be leaked with the block form.
As there is no maintainer for this lib, I can make a pull request with the following changes (with unit tests) if I'm asked to.
--
https://bugs.ruby-lang.org/
Unsubscribe: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>