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 ?


Asked by: Catherine155 | Posted: 28-01-2022






Answer 1

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);
}

Answered by: Brad353 | Posted: 01-03-2022



Answer 2

Yes ! That's it.

I have thought about that yesterday afternoon, and I went on the same solution. I'll post here my code for those who would be curious of that. 1) The NativeRMISecurityManager 2) The C code (you have to generate the .h with javah

(nb: I won't traduce it in english as there are loads of french comments)

package rmi;

import java.rmi.RMISecurityManager;

/**
 * <p> Ce SecurityManager, qui herite de RMISecurityManager,
 * implemente une verification supplementaire des droits
 * d'acces aux fichiers.
 * A la creation du SecurityManager et lors de la creation
 * de nouveaux threads, on renseigne ThreadLocal du nom du
 * _user_ du thread.
 * <p>Ainsi, lors des checkRead() et checkWrite()
 * notre SecurityManager appelle une methode native (JNI)
 * qui va verifier directement si le user a les droits 
 * d'acces a la ressource. 
 * <p><b>Warning : NE PAS OUBLIER DE FAIRE APPEL A 
 * setCurrentUser() DANS CHAQUE THREAD CREE.</b>
 * <p> <b>Remarque :</b> Pour les informations sur la compilation 
 * et l'execution de la lib ecrite en C, cf. le fichier README. 
 * @author a_po
 */
public class NativeRMISecurityManager extends RMISecurityManager
{
    private boolean unix;
    protected ThreadLocal user = new ThreadLocal();

    /**
     * Constructeur par defaut.
     * <p><b>ATTENTION :</b> Bien faire appel a la methode setCurrentUser(String) !
     * Sinon le SecurityManager se comportera comme un RMISecurityManager classique.
     * @see public void setCurrentUser(String userName)
     */
    public NativeRMISecurityManager()
    {
        super();
        String OS = System.getProperty("os.name").toLowerCase();
        unix = (OS.compareTo("windows") != 0); /* Si le systeme 
                                                    * n'EST PAS windows, 
                                                    * alors c'est UNIX...
                                                    * 
                                                    * Pas tres rigoureux,
                                                    * mais sinon il faut tester
                                                    * Systeme V, Linux, *BSD,
                                                    * Sun OS, ...
                                                    */

        /*
         * User du ThreadLocal : Chaque thread est considere comme ayant des
         * droits d'acces au systeme potentiellement differents.
         */
        this.user.set(user);

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


    /**
     * Verification en lecture.
     * <p>
     * Dans le cas ou l'on est sur une plateforme POSIX,
     * on souhaite verifier que le _user_ du Thread a le droit
     * de lecture sur le fichier.
     * <p>
     * De plus, dans le cas ou user est null, cela signifie
     * OBLIGATOIREMENT que le thread a ete cree "automatiquement"
     * et que le thread courant n'est pas un thread de "tache a executer".
 * <p>
     * En effet, le user est recupere dans le ThreadLocal
     * et on force l'initialisation de cette variable a l'instanciation
     * du SecurityManager (en mettant le constructeur par defaut prive) ou
     * en faisant appel a setCurrentUser(String)
     * @see void rmi.NativeRMISecurityManager.setCurrentUser(String user)
     */
    public void checkRead(String file)
    {
        super.checkRead(file);

        String str_user = (String)this.user.get();

        if (unix && str_user != null)
        {
            if (file == null)
            {
                throw new SecurityException("file = NULL !!!");
            }

            int ret = c_checkRead(file, str_user);
            if (ret != 0)
            {
                throw new SecurityException("Erreur d'acces au fichier : "     + file);
            }
        }
    }

    /**
     * Verification d'acces en ecriture sur un fichier.
     * @see void rmi.NativeRMISecurityManager.checkRead(String file)
     */
    public void checkWrite(String file)
    {
        super.checkWrite(file);
        String str_user = (String)this.user.get();

        if (unix && str_user != null)
        {
            if (file == null)
            {
                throw new SecurityException("file = NULL !!!");
            }

            int ret = c_checkWrite(file, str_user);
            if (ret != 0)
            {
                throw new SecurityException("Erreur d'acces au fichier : "         + file);
            }
        }
    }

    /**
     * Configure le thread courant pour que le user soit pris en compte
     * dans les verifications d'acces aux fichiers.
     * @param user
     */
    public void setCurrentUser(String userName)
    {
        this.user = new ThreadLocal();
        this.user.set(userName);
    }

    public String getCurrentUser()
    {
        if (user!=null){
            return (String)user.get();
        }
        else return null;
    }

    /**
     * Methode native a implementer en C.
     * @param file
     * @param user
     * @return 0 si ok <p> -1 sinon
     */
    public native int c_checkRead(String file, String user);

    /**
     * Idem que pour c_checkRead
     * @param file
     * @param user
     * @return
     * @see int rmi.NativeRMISecurityManager.c_checkRead(String file, String user)
     */
    public native int c_checkWrite(String file, String user);

    /**
     * Chargement de la bibliotheque JNI.
     */
    static
    {
        System.loadLibrary("rmi_NativeRMISecurityManager");
    }
}

And the C library:

#include <stdio.h>
#include <jni.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <stdlib.h>
#include <grp.h>
#include <string.h>
#include "rmi_NativeRMISecurityManager.h"

/* Droits en lecture / ecriture / execution */

#define R_RIGHT 4
#define X_RIGHT 1
#define W_RIGHT 2

JNIEXPORT jint JNICALL Java_rmi_NativeRMISecurityManager_c_1checkRead
  (JNIEnv *env, jobject obj, jstring file, jstring user)
{
    int ret = check_permission(env, obj, file, user);
    /**
     * La permission d'acces a un fichier vaut ceci :
     * 1 pour l'execution
     * 2 pour l'ecriture
     * 4 pour la lecture.
     * Donc :
     * * Droit en lecture : 4, 5, 6, 7
     * * Droit en ecriture : 2, 3, 6, 7
     * * Droit en execution : 1, 3, 5, 7.
     */
    if (ret == R_RIGHT || ret == R_RIGHT + W_RIGHT || 
        ret == R_RIGHT + X_RIGHT || ret == R_RIGHT + W_RIGHT + X_RIGHT)
    {
        return 0;
    }
    else
        return -1;
}

JNIEXPORT jint JNICALL Java_rmi_NativeRMISecurityManager_c_1checkWrite
  (JNIEnv *env, jobject obj, jstring file, jstring user)
{
    int ret = check_permission(env, obj, file, user);
    /**
     * La permission d'acces a un fichier vaut ceci :
     * 1 pour l'execution
     * 2 pour l'ecriture
     * 4 pour la lecture.
     * Donc :
     * * Droit en lecture : 4, 5, 6, 7
     * * Droit en ecriture : 2, 3, 6, 7
     * * Droit en execution : 1, 3, 5, 7.
     */
    if (ret == W_RIGHT || ret == W_RIGHT + R_RIGHT || 
        ret == W_RIGHT + X_RIGHT || ret == W_RIGHT + R_RIGHT + X_RIGHT)
    {
        return 0;
    }
    else
        return -1;
}


int check_permission(JNIEnv *env, jobject obj, jstring file, jstring user)
{
    struct stat pstat;
    const char* pzcfile = (*env)->GetStringUTFChars(env, file, 0);
    const char* pzcuser = (*env)->GetStringUTFChars(env, user, 0);
    struct passwd* puserInfo;
    int bisOwner = 0;
    int bisGroup = 0;
    struct group* pgroupInfo;
    int i;
    int droits = 0;

    /* recuperer les informations relatives au fichier */
    if(lstat(pzcfile, &pstat)<0)
    {
        fprintf(stderr,"* Le fichier %s n'exite pas.\n", pzcfile);
        (*env)->ReleaseStringUTFChars(env, file, pzcfile);
        (*env)->ReleaseStringUTFChars(env, user, pzcuser);
        return -1;
    }

    /* recuperer l'identifiant du user */
    puserInfo = getpwnam(pzcuser);
    if(puserInfo == NULL)
    {
        fprintf(stderr,"* L'utilisateur %s n'est pas connu du systeme.\n", pzcuser);
        (*env)->ReleaseStringUTFChars(env, file, pzcfile);
        (*env)->ReleaseStringUTFChars(env, user, pzcuser);
        return -2;
    }

    /* regarder si le user est proprietaire du fichier */
    if(puserInfo->pw_uid == pstat.st_uid)
    {
        bisOwner = 1;
    }
    /* si le user n'est pas proprietaire, verifier s'il est membre du groupe */
    if(!bisOwner)
    {
        /* recuperer les informations relatives au groupe */
        pgroupInfo = getgrgid(pstat.st_gid);
        /* parcourir la liste des membres du groupe a la recherche du user */
        for(i=0;;i++)
        {
            if(pgroupInfo->gr_mem[i] == NULL)
            {
                break;
            }
            if(strcmp(pgroupInfo->gr_mem[i],pzcuser) == 0)
            {
                bisGroup = 1;
                break;
            }
        }
    }

    /* recuperer les droits correspondants au user */
    if(bisOwner)
    {
        droits = (pstat.st_mode & S_IRWXU) >> 6;
    }
    else if(bisGroup)
    {
        droits = (pstat.st_mode & S_IRWXG) >> 3;
    }
    else
    {
        droits = pstat.st_mode & S_IRWXO;
    }

    /* liberer les espaces memoire alloues */
    (*env)->ReleaseStringUTFChars(env, file, pzcfile);
    (*env)->ReleaseStringUTFChars(env, user, pzcuser);
    return droits;
}

Thank you very much Greg Case. This comfort me because we have found the same solution. :)

Answered by: Haris560 | Posted: 01-03-2022



Similar questions

java - Eclipse buildpath automatically taking all JARs of a internal directory

How do I configure my project buildpath to have a set of .jar files located in the same directory automatically included in the buildpath ? Meaning that adding a new .jar file to this directory (and refreshing the project) updates the buildpath ? Rem : I am not working in a Webapp but in a standalone Java app. I know that it is possible in a Dynamic Web Project to have all the .jars located in WEB-INF/lib to be included in...


java - Automatically size JPanel inside JFrame

I have a JPanel subclass on which I add buttons, labels, tables, etc. To show on screen it I use JFrame: MainPanel mainPanel = new MainPanel(); //JPanel subclass JFrame mainFrame = new JFrame(); mainFrame.setTitle("main window title"); mainFrame.getContentPane().add(mainPanel); mainFrame.setLocation(100, 100); mainFrame.pack(); mainFrame.setVisible(true); mainFrame.setDefaultClose...


java - How to automatically generate N "distinct" colors?

I wrote the two methods below to automatically select N distinct colors. It works by defining a piecewise linear function on the RGB cube. The benefit of this is you can also get a progressive scale if that's what you want, but when N gets large the colors can start to look similar. I can also imagine evenly subdividing the RGB cube into a lattice and then drawing points. Does anyone know any other methods? I'm ruling out ...


java - automatically calculating column width in JTable

What's the preferred way of this (based on header &amp; contents width I believe)?


java - Automatically refactor a loop into a recursive method?

Do you know a tool that automatically refactors a method with a single loop into a recursive method, preferably in Java? This is for teaching purposes.


java - DataNucleus: How do I automatically perform actions when commiting a transaction?

Consider having an application using DataNucleus with the following persistence structure: You have a class that has two attributes like created (Date) and lastUpdate (Date): How do you automatically fill those attributes with the appropriate values when an object of this class is going to be committed? Appropriate values would be setting both, created and lastUpdate to the curr...


java - Automatically generating source and doc jars in Netbeans

Is there a way to automatically generate source and javadoc jars in Netbeans? Ideally I would like to place jars of my source and JavaDoc in the dist folder each time i build.


Java Class file from SVN to Tomcat on server: How to compile automatically?

I'm currently fixing a JSP project and it currently has a seemingly random collection of .class files in it's Tomcat's WEB-INF folder. As a way of simplifying this, I was planning to get .java files from these classes straight from SVN to WEB-INF folder on server and I got that to work but what would be the simplest way of compiling these? Of course I could create a cronjob which would compile all the classes during the ni...


java - Can I automatically execute JUnit testcases once with all logging enabled and once with all logging disabled?

I've found a solution, see my own answer below. Does anyone have a more elegant one? I want to do this to increase code-coverage and find subtle bugs. Assume the following class to be tested: public class Foo { private final Logger logger = LoggerFactory.getLogger(Foo.class); public void bar() { String param=[..]; if(logger.isInfoEnabled()) logger.info("A mes...


java - Tomcat shuts down automatically

I'm using Apache and Tomcat on a Windows server and since this morning, Tomcat stops working without any logs. It doesn't hang, it just shut down. There's no log in Tomcat, the CPU/Memory are fines, there are no System.Exit in my code. Anybody ever had this problem? It happens at random, after 5-10 minutes. The application responds normally and sometime, boom.. stops working. UPDAT...






Still can't find your answer? Check out these amazing Java communities for help...



Java Reddit Community | Java Help Reddit Community | Dev.to Java Community | Java Discord | Java Programmers (Facebook) | Java developers (Facebook)



top