HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 
Using JNI for Safer Java Servers Under UNIX, by Micah Silverman

For those of you not too familiar with the UNIX way of life, here's a brief overview. There are really two categories of accounts under UNIX: the superuser (named root) and everything else. Being root on a UNIX machine gives you the keys to the kingdom. You can remove files created by any other user, for instance. You can stop running processes started by root or any other user. A UNIX system can be entirely compromised if an unauthorized person or process gains root access.

One of the more common ways to do this is to exploit bugs in server processes that are running as the superuser. Generally, if no extra code has been put in place to prevent it, subprocesses spawned by a process running as root will also be running as root.

So Why Run Anything As Root?
As a protection against unauthorized common users hijacking a UNIX machine, processes that need to bind to low ports (0-1024) must be run as root. Web servers, for instance, run on port 80 by default. Without this protection any old user could start up a process that listened on port 80.

One of the common ways to avoid the risk of running as root is to switch to a different userid after binding to a low port. Remember, the superuser can do just about anything, including assuming the identity of another user. UNIX has a system call, setuid(), that allows the userid of a process to be changed to another one. So, typically, a process would start as root, bind to the appropriate port, and then call setuid()with the appropriate userid to switch to a less privileged user. If the process is compromised in this scenario, damage is limited to those processes and files that the less privileged user has access to.

What Does Java Know from UID?
Java is platform independent. Java applications run inside the Java Virtual Machine. The JVM has no internal representation for the platform-specific concept of UID.

The creators of Java understood that sometimes platform-specific operations would need to take place. This is where JNI (Java Native Interface) comes in.

Nuts and Bolts of JNI
I had a situation where we needed to ensure that the Java process could run as an unprivileged user but still bind to a low port – in this case the standard ftp port (21). Since the platform is UNIX (Linux, in particular), a BindException would be thrown when the thread running in the JVM attempts to create a ServerSocket bound to port 21 if the JVM is running as an unprivileged user.

I was able to use JNI to call the native setuid() and switch to the unprivileged user after the ServerSocket had been created and bound to port 21. Further, since my development environment is on Windows NT, and in order to maintain the cross-platform capability of Java, I created native stub code for the Windows platform as well. Following are the steps involved.

  1. Create Java code with native methods (see Listing 1).
  2. Compile Java code with javac (i.e., javac UID.Java).
  3. Generate header file with javah (i.e., javah-jni micah.util.UID; see Listing 2).
  4. Create native C module to implement the methods declared in the Java code (see Listings 3, 4).
  5. Compile the native code.
  6. Create a test Java app (see Listing 5).

Create Java Code with Native Methods
Referencing native code in Java is very straightforward (see Listing 1): simply include the keyword native in the signature of an empty method declaration terminated with a semicolon:

public static native int setuid(int uid);
In my example these methods are static because I don't need to maintain an object in order to use these methods. Notice the lines:
static {
System.loadLibrary("uid");
}
One of the nice things about JNI is that the actual loading of the native library is handled in a platform-independent way. The name of the library and its location is what is platform dependent. In the case of UNIX a file named libuid.so is expected to be in the LD_LIBRARY_PATH. In the case of Windows a file named uid.dll is expected to be in the system path.

This is declared as a static initializer to ensure that the library is loaded before methods of the class are referenced.

Generate Header File with Javah
Java comes with a utility called javah that is used to generate a .h file for use with C programs (see Listing 2). The nice thing when building an app that uses JNI is that you don't have to memorize the JNI calling conventions, structure names and return types. javah generates a file that has the function definitions declared that you'll need to implement in your C program. javah works similarly to Java in terms of classpath and package arrangement. Listing 2 was created using the command:

javah -jni micah.util.UID

Here is an example of one of the function definitions from the generated file:

JNIEXPORT jint JNICALL Java_micah_util_UID_setuid
(JNIEnv *, jclass, jint);
Notice that the calling convention is directly related to the package arrangement of your Java class. If that changes for some reason, you'll need to rerun javah to get the proper include file.

Create Native C Module to Implement the Methods Declared
Because I want to maintain my ability to develop on Windows NT and deploy on UNIX, I have created two platform-dependent implementation files: unix_uid.c and win_uid.c (see Listings 3 and 4). These files will be compiled to libuid.so and uid.dll, respectively. Let us look at the UNIX code first. Notice the first two lines of the file:

#include <jni.h>
#include "micah_util_UID.h"
These references are required. The first file is found in the include directory of the Java distribution. The second is the file generated by javah.

There are a number of UID and GID manipulation system calls on UNIX. An in-depth discussion of these is outside the scope of this article. Suffice it to say that each UNIX call has been represented in my Java class (see Listing 1). Each of these calls follows a similar pattern. I set up the function just as outlined in the generated .h file (see Listing 2):

JNIEXPORT jint JNICALL
Java_micah_util_UID_setuid (JNIEnv * jnienv,
jclass j, jint uid)
{
return((jint)setuid((uid_t)uid));
}

The only thing different that I need to do from an ordinary call to setuid is to properly cast the parameter ( (uid_t)uid) and the return value ( (jint) ) based on the signature generated by javah for the function.

As far as the Windows code goes, all of the functions return zero, which is the return value for a successful completion of the system call under UNIX.

Compile the Native Code
I used the following command to compile the code under UNIX (Linux, in this case):

gcc \
-I/usr/local/java/include \
-I/usr/local/java/include/genunix \
-shared unix_uid.c -o libuid.so
I used the following command to compile the code under Windows NT:
cl -Ie:\jdk1.1.8\include -Ie:\jdk1.1.8\include\win32
-LD win_uid.c -Feuid.dll
Create a Test Java App
The test app shown in Listing 5 simply waits for input from stdin (so we can see what user the process is running as), calls UID.setuid(1010) (remember, it's static so we don't need an instantiated object), prints out a success message and does another read from stdin (so we can verify that the user has been changed). Figure 1 shows this in action.

Figure 1
Figure  1:

Notice that after the first time the Java application is stopped, it is running as root. After the second time the Java application is stopped, it is running as webadm because the native code has been called.

For more information check out Sun's JNI tutorial at java.sun. com/docs/books/tutorial/native1.1/index.html.

Author Bio
Micah Silverman took an interest in UNIX internals and networking during the course of his computer science studies in the late '80s. He has been developing Java applications since its release. He is a cofounder of the Applied Technology Group, an Internet development and consulting firm (www.appliedtechnologygroup.cc). He can be contacted at: [email protected]

	


Listing 1

micah/util/UID.java

package micah.util;

public class UID {

    public static final int SUCCESS = 0;
    public static final int FAILURE = -1;

    public static native int setuid(int uid);
    public static native int seteuid(int uid);
    public static native int setgid(int gid);
    public static native int setegid(int gid);
    
    static {
	System.loadLibrary("uid");
    }
}

Listing 2

micah/util/micah_util_UID.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class micah_util_UID */

#ifndef _Included_micah_util_UID
#define _Included_micah_util_UID
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     micah_util_UID
 * Method:    setuid
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_micah_util_UID_setuid
  (JNIEnv *, jclass, jint);

/*
 * Class:     micah_util_UID
 * Method:    seteuid
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_micah_util_UID_seteuid
  (JNIEnv *, jclass, jint);

/*
 * Class:     micah_util_UID
 * Method:    setgid
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_micah_util_UID_setgid
  (JNIEnv *, jclass, jint);

/*
 * Class:     micah_util_UID
 * Method:    setegid
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_micah_util_UID_setegid
  (JNIEnv *, jclass, jint);

#ifdef __cplusplus
}
#endif
#endif

Listing 3

micah/util/unix_uid.c

#include <jni.h>
#include "micah_util_UID.h"
#include <sys/types.h>
#include <unistd.h>
  
JNIEXPORT jint JNICALL
Java_micah_util_UID_setuid (JNIEnv * jnienv,
jclass j, jint uid)
{
    return((jint)setuid((uid_t)uid));
}

JNIEXPORT jint JNICALL
Java_micah_util_UID_seteuid (JNIEnv * jnienv,
jclass j, jint uid)
{
    return((jint)seteuid((uid_t)uid));
}

JNIEXPORT jint JNICALL
Java_micah_util_UID_setgid (JNIEnv * jnienv,
jclass j, jint gid)
{
    return((jint)setgid((uid_t)gid));
}

JNIEXPORT jint JNICALL
Java_micah_util_UID_setegid (JNIEnv * jnienv,
jclass j, jint gid)
{
    return((jint)setegid((uid_t)gid));
}

Listing 4

micah/util/win_uid.c

#include <jni.h>
#include "micah_util_UID.h"

JNIEXPORT jint JNICALL 
Java_micah_util_UID_setuid (JNIEnv * jn, jclass j,
jint uid)
{
	return(0);
}

JNIEXPORT jint JNICALL 
Java_micah_util_UID_seteuid (JNIEnv * jn, jclass j, 
jint uid)
{
	return(0);
}

JNIEXPORT jint JNICALL 
Java_micah_util_UID_setgid (JNIEnv * jn, jclass j, 
jint gid)
{
	return(0);
}

JNIEXPORT jint JNICALL 
Java_micah_util_UID_setegid (JNIEnv * jn, jclass j, 
jint gid)
{
	return(0);
}

Listing 5

testchUID.java

import java.io.*;
import micah.util.*;

class testchUID {
	public static void main(String[] args) {
		try {
			System.in.read();
		}
		catch (IOException ioe) {}
		int result=UID.setuid(1010);
		if (result == UID.SUCCESS) {
		    System.out.println("Success!");
		}
		else if (result == UID.FAILURE) {
		    System.out.println("Failure!");
		}
		try {
			System.in.read();
		}
		catch (IOException ioe) {}
	}
}

  
 
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.