JDK-4993360 : File.canWrite fails when the Java process is running setuid
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.io
  • Affected Version: 1.4.2_03,5.0
  • Priority: P4
  • Status: Closed
  • Resolution: Won't Fix
  • OS: generic,solaris
  • CPU: sparc
  • Submitted: 2004-02-12
  • Updated: 2004-06-12
  • Resolved: 2004-06-12
Related Reports
Relates :  
Description
This is a Unix-only issue, found on Solaris. The problem is demonstrated by running a java program from within a setuid script; the method File.canWrite() is returning false when it should be returning true.
 
User1 owns a file "junk.txt"; only User1 can write this file.  User1 has also enabled setuid permissions on a script "RunIt.sh".  [chmod ug+s RunIt.sh]. RunIt.sh runs a java class FileBug, which calls File.canWrite().

According to the setuid man page, any user should be able to run RunIt.sh and assume the privileges of User1, and therefore, should be able to write 'junk.txt'. User2, which does not itself have write permission on "junk.txt", runs RunIt.sh, which make User2 effectively User1. But, File.canWrite returns false.  In looking at the jdk source, File calls into an internal class FileSystem, which is implemented in native code.

The truss utility shows calls to 'access()', which we think FileSystem is calling.  'access()'  is manpaged to test permissions against the "logged in" user (User2), not the effective user (User1), and so does not support a set uid script.  We believe the fix is to check the file permissions using something other than 'access()'. 

Comments
PUBLIC COMMENTS -
18-06-2004

EVALUATION Running setuid is not the only problem. ACLs are another obvious issue, and that one is tougher to simulate, especially if you care about portability. The only way to really tell if you can write to a file is to open the file in write mode. But that might be dangerous and/or slow. There is a similar issue with 4052517, where the only way to really tell if a program is executable is to try to execute it. Fortunately, there is no canExecute method. ###@###.### 2004-03-01 glibc provides the extension euidaccess, which will take the setuid status into account. But that would only solve the problem for Linux, not Solaris. The simple solution of implementing canRead/canWrite by just trying to open the file for reading or writing at the C level is the right thing to do. This is dangerous to do with canWrite since it might truncate the file. So we have to be extra-careful when testing this fix. It looks, however, like the right fix. ###@###.### 2004-03-08 Further investigation reveals: - The approach of simply trying the requested operation is quite reliable. However, it is also expensive, being 2-5 times more expensive for local files, and up to 50 times as expensive for NFS files. - The "access" function, in the absence of running setuid, does appear to correctly deal with acls, even over NFS, removing another reason for using the "Just Open It" algorithm. Hopefully, other strange file systems such as AFS will also do the right thing for "access". - Another approach is to switch effective and read uid/gids with setreuid, setregid, call "access", and then switch back. This has a serious problem in the presence of concurrency. All other threads would need to be stopped or at least prevented from executing native code while the uids/gids are switched. This is unacceptably unreliable and/or slow. - Another alternative would be to fork(), setuid(geteuid()), call "access", then report the results to the parent, but this is likely to be even more expensive than the "Just Open It" algorithm. - The user can easily implement the "Just Open It" algorithm (see Workaround) Therefore, I intend to close this as Will Not Fix. ###@###.### 2004-03-31 There is another problem with the "Just Open It" algorithm. Opening a file for read will cause the file "access time" to be updated, which has performance and unwanted-side-effect issues. Basically, Posix is broken in this regard - there is no good way to find out whether a file is readable or writable. This is even true on Linux, where euidaccess is provided as part of libc. euidaccess uses not completely reliable heuristics (examining the Unix permission bits) when the process is running setuid. Support needs to be added to the kernel on both Linux and Solaris. This URL http://www.opengroup.org/onlinepubs/007904975/functions/access.html endorses the "Just Open It" algorithm when discussing why the Single Unix Specification does not include eaccess(): "It was also argued that problem (2) is more easily solved by using open(), chdir(), or one of the exec functions as appropriate and responding to the error, rather than creating a new function that would not be as reliable. Therefore, eaccess() is not included in this volume of IEEE Std 1003.1-2001." ###@###.### 2004-04-01 This bug got escalated. And the fix is committed to 1.4.2_06. Hence may need consideration for tiger too! ###@###.### 2004-05-05 After much discussion, my conclusion is this: - canRead/canWrite should be provided by the operating system; it is too hard for an application library to provide this functionality. - No one has ever implemented this completely correctly, despite many efforts such as glibc's. - I believe a robust solution is possible in the absence of OS support, but surprisingly difficult. It is unlikely to be worth the implementation and maintenance effort. ###@###.### 2004-06-11
11-06-2004

WORK AROUND The following implements the "Just Open It" algorithm in 100% Pure Java: static boolean canRead(String f) { try { new FileInputStream(f).close(); return true; } catch (IOException e) { return false; } } static boolean canWrite(String f) { try { new FileOutputStream(f, true).close(); return true; } catch (IOException e) { return false; } } ###@###.### 2004-03-31 Another workaround is to make the real and effective uids/gids identical. Presumably there is a wrapper setuid C program that invokes the java executable. In that program, simply add setuid(geteuid()) to main. Or do this via JNI from java. ###@###.### 2004-04-01
01-04-2004