Details
-
Type:
Bug
-
Status:
Resolved
-
Priority:
Major
-
Resolution: Fixed
-
Affects Version/s: JRuby 1.6
-
Fix Version/s: JRuby 1.7.0.pre1
-
Component/s: Core Classes/Modules
-
Labels:None
-
Environment:MacOS, Linux
-
Number of attachments :0
Description
When running with root access rights, temp dirs cannot be deleted.
require 'tmpdir'
Dir.mktmpdir do |d|
p d
end
Run fine as simple user, but as root.
sh-3.2# jruby foo.rb
"/tmp/d20110331-37927-ntr68q"
Errno::EACCES: Permission denied - /tmp/d20110331-37927-ntr68q/.
initialize at org/jruby/RubyFile.java:434
open at org/jruby/RubyIO.java:1116
remove_entry_secure at /opt/jruby/lib/ruby/1.8/fileutils.rb:699
mktmpdir at /opt/jruby/lib/ruby/1.8/tmpdir.rb:131
(root) at foo.rb:4
(same with sudo)
Issue Links
- is duplicated by
-
JRUBY-6178
Dir.mktmpdir("foo",&block) does not work
-
Activity
Ok, it looks like for whatever reason the tmpdir/fileutils logic falls through to different logic in this case, where it attempts to open the directory and delete the entry directly. See fileutils.rb line 699 in JRuby 1.6.
The JDK (and by extension JRuby) can't open directories as files. As a result, we throw Errno::EACCESS. It's not actually a permission problem...it's functionality we don't/can't support.
We'll need to patch fileutils.rb. I'm not sure about this "secure" code, so I'm going to have to study it a bit.
I think so, but the reporter says it works without root permission where I cannot confirm...
lucas, does "jruby -rtmpdir -e 'Dir.mktmpdir {}'" really work for you without root permission?
Hi,
lucasdicioccio@mbp:~$ rvm use jruby
Using /Users/lucasdicioccio/.rvm/gems/jruby-1.6.4
lucasdicioccio@mbp:~$ jruby -rtmpdir -e 'Dir.mktmpdir {}'
lucasdicioccio@mbp:~$ rvmsudo jruby -rtmpdir -e 'Dir.mktmpdir {}'
lucasdicioccio@mbp:~$ sudo jruby -rtmpdir -e 'Dir.mktmpdir {}'
Errno::EACCES: Permission denied - /tmp/d20110928-43452-b74wi3/.
initialize at org/jruby/RubyFile.java:442
open at org/jruby/RubyIO.java:1107
remove_entry_secure at /Users/lucasdicioccio/.rvm/rubies/jruby-1.6.4/lib/ruby/1.8/fileutils.rb:699
mktmpdir at /Users/lucasdicioccio/.rvm/rubies/jruby-1.6.4/lib/ruby/1.8/tmpdir.rb:131
(root) at -e:1
Apparently the problem arises using sudo and not rvmsudo. Oddly, the versions are the same (and, not shown, the path to jruby executable given with `which` are the same too):
lucasdicioccio@mbp:~$ jruby -v
jruby 1.6.4 (ruby-1.8.7-p330) (2011-08-23 17ea768) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_26) [darwin-x86_64-java]
lucasdicioccio@mbp:~$ rvmsudo jruby -v
jruby 1.6.4 (ruby-1.8.7-p330) (2011-08-23 17ea768) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_26) [darwin-x86_64-java]
lucasdicioccio@mbp:~$ sudo jruby -v
jruby 1.6.4 (ruby-1.8.7-p330) (2011-08-23 17ea768) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_26) [darwin-x86_64-java]
My tool is an executable JAR running at volunteers' home and a friend of mine found this bug first. I'm still shipping a jruby <1.6.4. I use a workaround for this problem by calling system commands in last resort. With this workaround, my tool is quite stable. I didn't feel the need for upgrading so far, hence I didn't try to package 1.6.4 in my tool.
lucas: Thanks for further investigation. I think you don't have a write permission to /tmp, so with "jruby -rtmpdir -e 'Dir.mktmpdir {}'" you're creating tmp directory in current dir, which has 0700 permission. It's safely removable by FileUtils so it works. Can you check permissions of each directory?
For the original issue, I created a patch at tmpdir-failure branch: https://github.com/jruby/jruby/commit/b8e126a4049c9aa12f9aa953a51fc9ffa5dcc38a
commit b8e126a4049c9aa12f9aa953a51fc9ffa5dcc38a
Author: Hiroshi Nakamura <nahi@ruby-lang.org>
Date: Fri Sep 30 23:14:48 2011 +0900
Make Dir.mktmpdir with block work
Dir.mktmpdir {} raises Errno::EACCES 'Permission denied' because it
calls FileUtils.remove_entry_secure internally and remove_entry_secure
tries to open directory for reading. It's not allowed in Java.
The reason why tmpdir calls FileUtils.remove_entry_secure instead of
FileUtils.remove_entry is for TOCTTOU file removing vulnerability I
guess, but mktmpdir creates temporary directory in 0700 mode which
should prohibit an attack for the vulnerability.
JRuby devs, how do you think?
lucas: jruby -rtmpdir -e 'Dir.mktmpdir
{ |dir| p dir }' may help to understand the difference.
I was wrong. It's important that the parent directory is 0700 or not.
Another try.
commit 56f7e9c10933453a4964d44367acc28b913ea7f4
Author: Hiroshi Nakamura <nahi@ruby-lang.org>
Date: Wed Oct 12 15:56:50 2011 +0900
Attempt to find non-world-writable tmpdir
In JRuby, Dir.mktmpdir {} raises Errno::EACCES 'Permission denied'
because it calls FileUtils.remove_entry_secure internally to delete the
created tmpdir and remove_entry_secure tries to open the tmpdir for
reading before removing contents in the tmpdir. Opening a directory for
reading is not allowed in Java.
FileUtils.remove_entry_secure tries to open the directory to avoid
TOCTOU file removing vulnerability. When the parent directory of a
tmpdir is world-writable, FileUtils.remove_entry_secure needs to open
the tmpdir. When the parent directory is NOT world-writable, it simply
calls FileUtils.remove_entry which works well with JRuby.
So this commit changed Dir.tmpdir and attempts to find a directory which
is not world-writable first. There's '.' in candidates and we can
generally expect it non-world-writable so Dir.mktmpdir {} should work.
Bear in mind that this change affects to Tempfile library (implemented
in Java for JRuby.) Files created by Tempfile.new is in Dir.tmpdir which
is changed by this commit.
For a workaround of this problem, users can set TMPDIR environment
variable to point to the directory which is not world-writable.
% jruby164 -rtmpdir -e 'Dir.mktmpdir { |dir| p dir }'
"/tmp/d20111012-18292-rouaol"
Errno::EACCES: Permission denied - /tmp/d20111012-18292-rouaol/.
initialize at org/jruby/RubyFile.java:442
open at org/jruby/RubyIO.java:1107
remove_entry_secure at /home/nahi/java/jruby-1.6.4/lib/ruby/1.8/fileutils.rb:699
mktmpdir at /home/nahi/java/jruby-1.6.4/lib/ruby/1.8/tmpdir.rb:131
(root) at -e:1
% TMPDIR=. jruby164 -rtmpdir -e 'Dir.mktmpdir { |dir| p dir }'
"/home/nahi/git/jruby/d20111012-18311-1fb1wl8"
%
I keep the branch 'tempfile-failure' there. Next JRuby dev who visit this ticket should decide to apply/reject it.
I'll commit when I get a LGTM.
As I wrote in this commit, there's a workaround. Users can set TMPDIR environment variable to point to the directory which is not world-writable.
% jruby164 -rtmpdir -e 'Dir.mktmpdir { |dir| p dir }'
"/tmp/d20111012-18292-rouaol"
Errno::EACCES: Permission denied - /tmp/d20111012-18292-rouaol/.
initialize at org/jruby/RubyFile.java:442
open at org/jruby/RubyIO.java:1107
remove_entry_secure at /home/nahi/java/jruby-1.6.4/lib/ruby/1.8/fileutils.rb:699
mktmpdir at /home/nahi/java/jruby-1.6.4/lib/ruby/1.8/tmpdir.rb:131
(root) at -e:1
% TMPDIR=. jruby164 -rtmpdir -e 'Dir.mktmpdir { |dir| p dir }'
"/home/nahi/git/jruby/d20111012-18311-1fb1wl8"
%
We have seen this reported on travis-ci.org: https://github.com/travis-ci/travis-ci/issues/312
I've just bumped into this on 1.7.0dev. Can I ask that a NotImplementedError be thrown instead of an Errno::EACCES, please? It's confusing to report that privileges are wrong when actually it's an implementation detail.
I'd offer a patch, but I'm afraid I can't identify where the specific exception is being thrown for this case.
You can reproduce this on travis-ci's vagrant boxes by creating a simple "test" to run the code from the op as jruby.
Maybe that'll help identify the problem?
I'm willing to work with someone to setup a simple git project with travis-ci if you want:
'docwhatNOSPAMFORMEgerf.org'.gsub(/[A-Z]+/, '@')
NaHi's commit looks good. I commented on the commit as well:
https://github.com/jruby/jruby/commit/56f7e9c10933453a4964d44367acc28b913ea7f4#commitcomment-1009896
Thanks, Nick. I merged the change to master branch. I'll update our stdlib fork as well.
commit 249d5470f8f1842fb11ae4d1cc51f8e66c3ee236
Author: Hiroshi Nakamura <nahi@ruby-lang.org>
Date: Fri Mar 2 15:49:31 2012 +0900
Attempt to find non-world-writable tmpdir
In JRuby, Dir.mktmpdir {} raises Errno::EACCES 'Permission denied'
because it calls FileUtils.remove_entry_secure internally to delete the
created tmpdir and remove_entry_secure tries to open the tmpdir for
reading before removing contents in the tmpdir. Opening a directory for
reading is not allowed in Java.
FileUtils.remove_entry_secure tries to open the directory to avoid
TOCTOU file removing vulnerability. When the parent directory of a
tmpdir is world-writable, FileUtils.remove_entry_secure needs to open
the tmpdir. When the parent directory is NOT world-writable, it simply
calls FileUtils.remove_entry which works well with JRuby. So this
commit changed Dir.tmpdir and attempts to find a directory which is not
world-writable first. There's '.' in candidates and we can generally
expect it non-world-writable so Dir.mktmpdir {} should work.
Bear in mind that this change affects to Tempfile library (implemented
in Java for JRuby.) Files created by Tempfile.new is in Dir.tmpdir which
is changed by this commit.
For a workaround of this problem, users can set TMPDIR environment
variable to point to the directory which is not world-writable.
% jruby164 -rtmpdir -e 'Dir.mktmpdir { |dir| p dir }'
"/tmp/d20111012-18292-rouaol"
Errno::EACCES: Permission denied - /tmp/d20111012-18292-rouaol/.
initialize at org/jruby/RubyFile.java:442
open at org/jruby/RubyIO.java:1107
remove_entry_secure at /home/nahi/java/jruby-1.6.4/lib/ruby/1.8/fileutils.rb:699
mktmpdir at /home/nahi/java/jruby-1.6.4/lib/ruby/1.8/tmpdir.rb:131
(root) at -e:1
% TMPDIR=. jruby164 -rtmpdir -e 'Dir.mktmpdir { |dir| p dir }'
"/home/nahi/git/jruby/d20111012-18311-1fb1wl8"
%
I managed to change CRuby's tmpdir behavior: https://github.com/ruby/ruby/commit/bcb9e567c422f535b4871ce2795179af808d0077
I'll merge this for jruby 1.7 and for 1.6.8.
NB: With new tmpdir implementation, it uses the directory that is (world_writable? and !stickey?). This directory is safely removable against TOCTOU attack so tmpdir.rb does not call FileUtils.remove_entry_secure, which tries to open directory as a file, which does not work on Java platform.
Oops. It attempts to use the directory that is (!world_writable? or stickey?).
The attack vector is the: (world_writable? and !stickety?)
I once introduced the upstream change to master but CI tells that some tests from CRuby directly calls Dir.mktmpdir and FileUtils.remove_entry_secure, so tmpdir.rb needs to create tmpdir at non-world-writable directory regardless of stickey bit. Hmm.
Closing this issue because the root problem is gone. Syncing with CRuby's lib is different issue and should be solved when other problem happens.
I'm confused, and still getting the error on 1.6.8.
In order for this to work I need to override the TMPDIR to point to something that is not world writable?
Confirmed. Weird! I'll poke at it a bit.