JDK-4420020 : File.canWrite returns invalid results as Windows 2000 User
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.io
  • Affected Version: 1.1.8,1.2.2,1.4.0
  • Priority: P4
  • Status: Closed
  • Resolution: Won't Fix
  • OS: windows_nt,windows_2000
  • CPU: x86
  • Submitted: 2001-02-28
  • Updated: 2010-11-20
  • Resolved: 2010-11-20
Related Reports
Relates :  
Description
Name: mt13159			Date: 02/28/2001


A problem has arisen in conjunction with File's canWrite method and
members of group User under Windows 2000.

Like NT, Windows 2000 has an Administrator group.  Win2K also has a
Power Users group, which is akin to a standard user under NT.  New to
Win2K is the Users group, which is severely restricted...

  Members of group User can read the entire Registry, but can only
  cause updates to occur in HKEY_CURRENT_USER.

  Members of group User cannot write to C:\WinNT or C:\Program Files
  or to subdirectories of C:\WinNT or C:\Program Files, unless the
  Administrator has specifically granted such privileges.

  To use a generic example, let's say a user gets a new machine with
Windows 2000 installed.  The machine is missing Netscape.  A member
of group User cannot install Netscape as it modifies Registry entries
outside of HKEY_CURRENT_USER, and typically installs to the C drive's
  Program Files directory.  An Administrator would have to install the
software, and configure it for the User.

Now for the Java problem.  Class File contains the method canWrite,
which determines write accessibility for a file or directory.  In the
underlying C code, canWrite calls access.  access checks the file or
directory's permissions, and returns 0 for success or -1 for failure.

The problem is that access *ONLY* checks the file or directory's
permissions.  It knows nothing about system security.

By default, C:\Program Files has the A and R attributes set.  R says
the directory is not writable.  Calling canWrite on this directory
returns false, as the directory's permissions do not allow writing.

But the C:\WinNT directory, and most of the directories inside of
C:\Program Files, do not have the R attribute set, meaning they are
available for writing.  Calling canWrite on these directories returns
true, suggesting they're available for writing when, in fact they are
not because of the additional layer of Windows 2000 security.

Example...

import java.io.File;

public class FileTest
{
  public static void main(String[] args)
  {
    if (args.length != 1) {
      System.out.println("usage:  java FileTest filename");
      System.exit(1);
    }
    File file = new File(args[0]);
    if (file.exists() == true) {
      System.out.println("exists");
      if (file.canWrite() == true) {
        System.out.println("can write");
      } else {
        System.out.println("can't write");
      }
    } else {
      System.out.println("doesn't exist");
    }
  }
}

If the above code is run by a member of group User, as follows, the
results are incorrect...

java FileTest c:\winnt
exists
can write

This is wrong.  A member of group User does not have write access to
C:\WinNT.

java FileTest "c:\program files"
exists
can't write

This is correct, only because the of the R attribute on the Program
Files directory.

java FileTest "c:\program files\common files"
exists
can write

This is wrong.  A member of group User does not have write access to
this directory.

Here's an example specific to C...

#include <stdio.h>

void main(int argc, char **argv)
{
  if (argc != 2) {
    printf("usage:  filetest filename\n");
    exit(1);
  }
  if (access(argv[1], 0) == 0) {
    printf("exists\n");
    if (access(argv[1], 2) == 0) {
      printf("can write\n");
    } else {
      printf("can't write\n");
    }
  } else {
    printf("doesn't exist\n");
  }
}

As above, the results of this code are wrong.  The test for write
accessibility doesn't work, as access simply doesn't know to test for
system security.

I recommend testing this with a fresh version of Windows 2000.  This
way you'll be testing with a system that hasn't been altered by the
Administrator, giving write access to members of group User.

After performing the install, create a new user.  Add this person to
the Users group, log out, log back in, and run your tests.
(Review ID: 117807) 
======================================================================

Additional information provided by licensee:

The workaround I provided turns out to not be a complete fix.  There
are a number of other issues that I came across after filling the bug.

First, the Windows specific code snippet I provided is only capable of
determining whether the user has write permission based upon system
security.  It doesn't look at file permissions, which may differ from
system security.

In other words, the Windows specific code may determine a file system
is writable, but it can't tell whether or not the file is read only.
So it's still necessary to test file permissions using access().

Additionally, there are issues not only with canWrite, but also the
other functions such as canDelete.  This is a little weird, so bear
with me.

If you set a Windows directory to be read only, you would expect the
contents of the directory to not be writable.  Unfortunately, this
isn't the case.  "attrib +r directory" only prevents the directory
from being renamed, moved or deleted.  It doesn't prevent a user from
creating, modifying or deleting files from within the directory.

As a result, canWrite and canDelete may not return proper results.

Overall, the classes for testing files or file systems don't exactly
work properly on Windows due to quirks in the OS.

Comments
EVALUATION File's canRead/canWrite methods return a result based on the value of the DOS readonly attribute. There are no plans to change this (for compatability reasons). The equivalent in the new file system is the checkAccess method which can be used to check that the application has the given access to the file. On Windows this computes the effective access to the file (uses the file's ACL and DOS readonly attribute) and so will give the result expected by the submitter.
13-08-2009

WORK AROUND Name: mt13159 Date: 02/28/2001 I can only think of one viable solution. In io_md.c, define a new function sysAccessWrite. This function takes a path as its sole argument. Define the function in sys_api.h. In filesystem.c, change java_io_File_canWrite0 to call the new function. Here are the contents of the new function... int sysAccessWrite(const char *path) { DWORD result; SECURITY_INFORMATION secInfo = DACL_SECURITY_INFORMATION; PSECURITY_DESCRIPTOR secDesc; PACL dacl; TRUSTEE trustee; ACCESS_MASK rights; OSVERSIONINFO vers; char pathbuf[MAX_PATH]; if (strlen(path) > MAX_PATH - 1) { errno = ENAMETOOLONG; return -1; } // Test OS version. We only need this code on Windows 2000. vers.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if (GetVersionEx(&vers) == 0) { // set errno = an appropriate error code return -1; } if (!(vers.dwMajorVersion == 5 && vers.dwMinorVersion == 0)) { // If not Windows 2000, we can just use sysAccess. return sysAccess(path, W_OK); } // Get the discretionary access control list. result = GetNamedSecurityInfo(argv[1], SE_FILE_OBJECT, secInfo, NULL, NULL, &dacl, NULL, &secDesc); if (result != ERROR_SUCCESS) { // set errno = an appropriate error code return -1; } // Determine the current user's access rights. trustee.pMultipleTrustee = NULL; trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; trustee.TrusteeForm = TRUSTEE_IS_NAME; trustee.TrusteeType = TRUSTEE_IS_USER; trustee.ptstrName = "CURRENT_USER"; result = GetEffectiveRightsFromAcl(dacl, &trustee, &rights); if (result != ERROR_SUCCESS) { // set errno = an appropriate error code return -1; } // Free the security descriptor. (void) LocalFree(secDesc); // Return the result. return (rights & FILE_WRITE_DATA) ? 0 : -1; } There are problems with this solution. First, it can't be used with MSCV++ 4.2 (required by JRE 1.1.8) as the necessary functions and function definitions are in MSVC++ 5.0 and higher. Second, it's rather time consuming. Alternatively the sysAccessWrite function could instead use open to determine write accessibility, but A) open can't be used to determine write accessibility of a directory, and B) it's hacky in the extreme to test for write accessibility by opening a file. On the other hand, a piece of code typically calls canWrite as a precursor to calling FileOutputStream. Users of the 1.1.x JRE can code such that they bypass canWrite and just use FileOutputStream directly. If FileOutputStream can't open a file for writing, it'll return an IOException. The trick is to explicitely catch IOException rather than catching the generic Exception. The application can then either prompt the user for a writable directory, or fail in a manner appropriate to the application. Note that canWrite uses access in all versions of the JRE. Hence, 1.3 and 1.4 are subject to the same problem when running as a member of group User under Windows 2000. ======================================================================
11-06-2004

EVALUATION This bug has been added to the must-fix list for Oracle and the JInitiator replacement with JPI. james.melvin@East 2001-05-18 Today, oracle has indicated that they no longer require this bug to be fixed in order to adopt JPI instead of JInitiator. Ignore the above statement. james.melvin@East 2001-05-24 There doesn't seem to be a native API that gives the correct answer; access/_waccess are only approximations. As on Unix, the idea of simply trying to open a file for reading or writing is likely to be more accurate, but might introduce other problems. ###@###.### 2004-04-30
30-04-2004