Search code examples
javamultithreadingjava-native-interfacermisecuritymanager

How to automatically copy data to new RMI threads?


I am adapting a little rmi client-server application. I have written several things :

HelloInterface -> A Hello World interface for RMI
Server -> The server app'
Client -> The client app'

Nothing special, but... I have put my hands in a new RMISecurityManager, which calls a JNI method and checks the permission for a separate user:

package rmi;
import java.rmi.RMISecurityManager;
import java.io.*;

public class NativeRMISecurityManager extends RMISecurityManager
{
    private boolean unix;
    protected static ThreadLocal user = new ThreadLocal();

    /*
     * On interdit l'utilisation du constructeur par defaut
     * pour obliger l'utilisation du constructeur avec user. 
     */
    private NativeRMISecurityManager()
    {
        super();
    }

    public NativeRMISecurityManager (final String user)
    {
        super();
        String OS = System.getProperty("os.name").toLowerCase();
        unix = (OS.compareTo("windows") != 0); /* Assume that if not 
                            * windows, then "UNIX/POSIX" 
                            */
        /*
         * ThreadLocal's user : Each thread is considered 
         * to have different access rights to the machine
         */

        NativeRMISecurityManager.user.set(user);

        if (!unix)
        {
            System.out.println("Systeme : "+OS);
        }
    }

    public void checkRead(String file)
    {
        super.checkRead(file);
        /*
         * If we are on a **IX platform we want to check that 
         * the _user_ has access rights.
         */
        if (unix)
        {
            String str_user = (String)NativeRMISecurityManager.user.get();

            if (file == null)
            {
                throw new SecurityException("file = NULL !!!");
            }
        if (str_user == null)
            {
                throw new SecurityException("user = NULL in the ThreadLocal!!!");
            }

            int ret = c_checkRead(
                    file, 
                    str_user
                    );
            if (ret != 0)
            {
                throw new SecurityException("Access error: " + file);
            }
        }
    }

    public native int c_checkRead(String file, String user);
}

In the Server class I'm doing that :

String user = "my_user";
System.setSecurityManager(new NativeRMISecurityManager(user));

This class seems to work in the Server's main thread. Now the problem is when I try and connect to that Server class and lookup the Registry. I get that exception :

Exception in thread "RMI TCP Connection(1)-192.168.42.207"     java.lang.ExceptionInInitializerError
        at sun.rmi.transport.StreamRemoteCall.getInputStream(StreamRemoteCall.java:111)
        at sun.rmi.transport.Transport.serviceCall(Transport.java:118)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:466)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:707)
        at java.lang.Thread.run(Thread.java:595)
Caused by: java.lang.SecurityException: user = NULL dans le ThreadLocal!!!
        at rmi.NativeRMISecurityManager.checkRead(NativeRMISecurityManager.java:62)
        at java.io.File.exists(File.java:700)
        at java.lang.ClassLoader$3.run(ClassLoader.java:1689)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1686)
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1668)
        at java.lang.Runtime.loadLibrary0(Runtime.java:822)
        at java.lang.System.loadLibrary(System.java:993)
        at sun.security.action.LoadLibraryAction.run(LoadLibraryAction.java:50)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.server.MarshalInputStream.<clinit>(MarshalInputStream.java:97)
        ... 5 more

IMHO the meaning of this is that a thread is (implicitly) created and gets the NativeRMISecurityManager as its default SecurityManager.

Would somebody have any advice concerning that ?


Solution

  • IMHO the meaning of this is that a thread is (implicitly) created and gets the NativeRMISecurityManager as its default SecurityManager.

    This is true, though it is not the cause of your error; the problem has to do with the use of ThreadLocal. The key property of a ThreadLocal is that every calling thread has its own value. In this case "user" is being set by (and for) the thread that initializes the NativeRMISecurityManager and sets it as the System Security Manager (presumably the main thread).

    However, some other thread (by the look of it the RMI message handling thread) is calling checkRead() - but the "user" field was never set for this thread! Thus the value comes back as null.

    Unless there is some reason to have different threads have different values - and I cannot discern one from your example - I would recommend making the "user" field a) a String, not a ThreadLocal and b) non-static. That should solve your null value problem.

    However, presuming that it is a requirement / design constraint, the trouble with the current architecture is that only the thread that instantiates the NativeRMISecurityManager actually gets to have a user - every other thread gets null.

    In delving into this with a little more depth, I think I need a better understanding of your problem domain to offer any helpful suggestions as to a fix. Further, there is nothing quick or dirty about Java's Security Architecture. However I will do my best working under a few assumptions:

    1. Threads created by the system are assumed to be trusted
    2. Threads created by your code must specify a user
    3. Children threads should inherit the user of the creating thread

    Potential implementation:

    public class NativeRMISecurityManager extends RMISecurityManager {
    
        private static final boolean UNIX;
    
        static {
            String OS = System.getProperty("os.name").toLowerCase();
            UNIX = (OS.compareTo("windows") != 0); /* Assume that if not 
                                                    * windows, then "UNIX/POSIX" 
                                                    */
        }
    
        protected static InheritableThreadLocal<String> user =
            new InheritableThreadLocal<String>();
    
        public static setThreadUser(String username) {
            user.set(username);
        }
    
    
        public NativeRMISecurityManager(String initialUser) {
            super();
            // Set the user for the thread that constructs the security manager
            // All threads created as a child of that thread will inherit the user
            // All threads not created as a child of that thread will have a 'null' user
            setThreadUser(initialUser);
        }
    
    
        public void checkRead(String file) {
            super.checkRead(file);
            /*
             * If we are on a **IX platform we want to check that 
             * the _user_ has access rights.
             */
            if (UNIX)
            {
                if (file == null)
                {
                    throw new SecurityException("file = NULL !!!");
                }
    
                String str_user = NativeRMISecurityManager.user.get();
    
                if (str_user != null)
                {
                    // Note: sanitize input to native method
                    int ret = c_checkRead(file, str_user);
    
                    if (ret != 0)
                    {
                        throw new SecurityException("Access error: " + file);
                    }
                }
    
                // Assume a system thread and allow access
            }
        }
    
        public native int c_checkRead(String file, String user);
    }