Search code examples
javajava-native-interfacesetuid

Fail to seteuid in java by calling JNI


My app need to run as none-root user but need to switch to other user to execute some commands.

I tried to:

  1. write a JNI,

    JNIEXPORT void JNICALL Java_SetUIDJNI_setuid(JNIEnv *env, jobject thisObj,jstring uname) {
        const char *name = jstringTostring(env,uname);
        int pid;
        struct passwd *p;
        show_ids();
        if ((p = getpwnam(name)) == NULL) {
            perror(name);
            exit(EXIT_FAILURE);
        }
        pid = (int) p->pw_uid;
        printf("pid=%d\n",pid);
        if (seteuid (pid) < 0) {
            perror ("setuid");
            exit (EXIT_FAILURE);
        }
        show_ids();
    }
    
  2. build it as root and chmod u+s

    -rwsr-xr-x 1 root root  76155 Aug  7 16:56 libsetuid.so*
    
  3. call it in java

    api = new SetUIDJNI();  // invoke the native method
    api.setuid("slurm");
    

But if run it as none-root, it does not work

/opt/jdk1.8/jre/bin/java -Djava.library.path=../jni HelloJNI
 The real user ID is: 1000
 The effective user ID is :1000   <= which expected is 0
 setuid: Operation not permitted

But it works if runner is root

The real user ID is: 0
The effective user ID is :0
pid=1002The real user ID is: 0
The effective user ID is :1002

Anything wrong here?

UPDATE

Modify the JNI part to executable c

void show_ids (void)
 {
    printf ("The real user ID is: %d\n", getuid());
    printf ("The effective user ID is :%d\n", geteuid());
  }

 int main(void)
 {

    show_ids();
    if (seteuid (1002) < 0) {
      perror ("setuid");
      exit (EXIT_FAILURE);
    }
    show_ids();
    return (0);
  }

Build it as root and run chmod u+s

-rwsr-xr-x  1 root root 8814 Aug  9 11:44 a.out*

Run it as normal user and works

./a.out
The real user ID is: 1000
The effective user ID is :0
The real user ID is: 1000
The effective user ID is :1002

Solution

  • The reason that seteuid was not working for you from JNI was that the effective user id for the JVM process was not 0 (root).

    You were apparently attempting to make the effective user id by setting the "setuid" bit on your native library. That won't work. Similarly, making the JAR file (or a class file) as setuid won't work. The setuid bit is only meaningful on a executable file; i.e. a file that the OS itself knows how to execute.

    So how do we implement "setuid root behavior" for a Java program?

    • In theory, you could mark the /usr/bin/java as setuid to root. Don't do that! If you do that, every Java program you run will be run as root. That would be bad.

    • In theory, you could write a shell script to launch your application; e.g.

         #!/bin/sh
         java some.pkg.Main "$@"
      

      and mark the script as setuid to root. Don't do that! Setuid shell scripts are a security risk. (On some versions of Linux / UNIX the setuid bit is not respected for shell scripts anyway.)

    The solution is to write a custom JVM launcher in native code, compile and link it, and make the launcher executable setuid. Note that the launcher must be written very carefully to project against someone subverting it. For example:

    • It should ignore CLASSPATH environment variable, and should not allow the classpath to be supplied any other way.

    • It should not take a user-supplied JAR file as a parameter.

    • It should take care that the user can't trick it into running the wrong Java code by interfering with the path to the JAR file.

    • And other things ... that I haven't thought of!

    In fact, it is probably safer to run the Java application as a privileged user ... and not rely on "setuid root" at all.