First draft, Nov 5, 2002

Security

Java security provides some traps. Here are a few notes on my trial-and-error attempts to understand how it works.

  • Installing a security manager
  • A simple test program
  • What does a security manager do?
  • A simple policy file
  • Limiting file access
  • Some internals of FilePermission
  • Resources
  • Installing a security manager

    An application normally does not install a security manager. System.getSecurityManager() normally returns null. When there is no security manager, there are no security checks. Consequently, when you don't install a security manager, there are no restrictions.

    There may be situations when you want to install a security manager. A typical situation is RMI.

    Installing a security manager is simple:

    if (System.getSecurityManager() == null) // no security manager installed
    {
        System.setSecurityManager (new SecurityManager()); // install it
    }
    

    That's all about installing a standard security manager.

    You may also want to install your own security manager. This will be a subclass of java.lang.SecurityManager, and it will be installed in the same way.

    A simple test program

    To see the security manager in action, I wrote a very simple client. This client requires reading access to a specified file. Here is the source code of the client:

    import java.io.*;
    
    /**
     * A trivial file reader.
     * @author Margrit Höhme, 2002 
     * (<A HREF="mailto:java@mhoehme.de">java@mhoehme.de</A>)
     */
    public class SecurityTest
    {    
        public void read (String name)
            throws IOException
        {
            FileInputStream fin = new FileInputStream (name);
            int c;
            while ((c = fin.read()) != -1)
            {
                System.out.print ((char) c);
            }
            fin.close();
        }
         
        public static void main (String[] args)
            throws IOException
        {
            if (args.length < 1)
            {
                System.out.println 
                    ("Please specify a file name: SecurityTest text.txt");
                System.exit (0);
            }
            new SecurityTest().read (args[0]);
        }
    }
    
    This program ran fine for any file in my system (W2K):
    $ java SecurityTest test.txt
    $ java SecurityTest ../test.txt
    $ java SecurityTest C:\test.txt
    
    If the specified file was not there, a java.io.FileNotFoundException was thrown.

    What does a Security Manager do?

    Then I installed a security manager. Here is the modified main() method:

        public static void main (String[] args)
            throws IOException
        {
            if (args.length < 1)
            {
                System.out.println 
                    ("Please specify a file name: SecurityTest text.txt");
                System.exit (0);
            }
            if (System.getSecurityManager() == null)
            {
                System.setSecurityManager (new SecurityManager());
            }  
            new SecurityTest().read (args[0]);
        }
    
    Although it is invoked without a policy file, the program works fine for all files located in the same directory as the class file, or in a subdirectory. These locations also go under the name of the Java "Sandbox":
    $ java SecurityTest test.txt
    $ java SecurityTest ./test.txt
    $ java SecurityTest test/test.txt
    
    But an java.security.AccessControlException was thrown for files located in other directories, for example:
    $ java SecurityTest ../test.txt
    
    The AccessControlException is even thrown if the specified file does not exist! Therefore, if you get a FileNotFoundException, it is not a security-related problem, since the security checks have already been passed.

    When a Security Manager is installed, the program may only access files located in the "sandbox", that is in the same directory as the class file, or in a sub-directory. In any other cases, an AccessControlException is thrown, irrespective of the fact that the file may not even exist.


    A simple policy file

    Next I created a policy file with the policy tool, which is part of the JDK:

    /* AUTOMATICALLY GENERATED ON Tue Nov 05 16:13:07 CET 2002*/
    /* DO NOT EDIT */
    
    grant {
      permission java.io.FilePermission "<<ALL FILES>>", "read, write, delete, execute";
    };
    

    A policy file is a simple text file. There are no naming restrictions, nor must they be located in a specific directory. However, you will get an AccessControlException if the specified policy file does not exist. If the policy file does not exist, you won't get a special exception. A non-existing policy file is actually the same situation as no policy file: the files in the sandbox (current directory and subdirectories) are still accessible.

    A policy file is specified with the command-line option -Djava.security.policy:

    $ java -Djava.security.policy=test.policy SecurityTest ../test.txt
    
    Now the program worked fine again, for any file at any location.


    Limiting file access

    I did not like <<ALL FILES>>, and sought to replace it with something more restricted.

    Remark: The following tests were all run from a subdirectory of C:\Projekte\tests. For this reason the file C:\Projekte\tests\test.txt is not located within the sandbox. On the use of wildcards, see Some Internals.

    I found that my reader worked when I exactly named the file:

    grant 
    {
      permission java.io.FilePermission "C:\\Projekte\\tests\\test.txt", "read";
    };
    
    Similarly:
    grant 
    {
      permission java.io.FilePermission "C:\\Projekte\\tests\\*", "read";
    };
    
    But the following policy files throw an AccessDeniedException. Neither directories nor DOS-like wildcards work (for accessing directories, see Some Internals):
    grant 
    {
      permission java.io.FilePermission "C:\\Projekte\\tests\\*.txt", "read";
    };
    
    grant 
    {
      permission java.io.FilePermission "C:\\Projekte\\tests\\", "read";
    };
    
    Even files in subdirectories are not accessible (attempt to read C:\Projekte\tests\classes\test.txt from C:\Projekte\classes failed and threw an AccessControlException):
    grant {
      permission java.io.FilePermission "C:\\Projekte\\*", "read";
    };
    


    Some internals of FilePermission

    The creator of a java.io.FilePermission requires two parameters:

    FilePermission (String path, String actions)
    
    Whereas the actions ("read", "write", "execute", "delete") are fairly obvious, the path parameter behaves more idiosyncratic.

    Here is an extract of the java.io.FilePermission doc:

    Pathname is the pathname of the file or directory granted the specified actions. A pathname that ends in "/*" (where "/" is the file separator character, File.separatorChar) indicates all the files and directories contained in that directory. A pathname that ends with "/-" indicates (recursively) all files and subdirectories contained in that directory. A pathname consisting of the special token "<<ALL FILES>>" matches any file.
    Here is what it says in short:
    target scope example
    <<ALL FILES>> any file (at any location) permission java.io.FilePermission "<<ALL FILES>>", "read";
    pathname a specific file permission java.io.FilePermission "c:\test\readme.txt", "read";
    a pathname ending in /* all files in the specified directory permission java.io.FilePermission "C:\test\*", "read";
    a pathname ending in /- all files plus subdirectories permission java.io.FilePermission "C:\test\-", "read";
    "Pathname" refers to any path - be it a file or a directory.

    The ultimate source is usually the source code itself. The source of most java classes is included in the JDK (src.zip).

    A FilePermission is created using the "target" of the policy file. The target is evaluated in the private init() method of java.io.FilePermission. The evaluation is based on the following constants:

        private static final String RECURSIVE = "-";
        private static final String WILD = "*";
        private static final String SEP_RECURSIVE = File.separator+RECURSIVE;
        private static final String SEP_WILD = File.separator+WILD;
    
    The init() method initialises two variables on this basis:
    boolean recursive;
    boolean directory;
    
    These two variables are used for checking the access rights. More details can be found in the source code of java.io.FilePermission and the related classes.


    Resources


    Contact: Margrit Höhme, java@mhoehme.de

    Last modified: