| By Micah Silverman | Article Rating: |
|
| October 1, 2000 12:00 AM EDT | Reads: |
11,034 |
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.
- Create Java code with native methods (see Listing 1).
- Compile Java code with javac (i.e., javac UID.Java).
- Generate header file with javah (i.e., javah-jni micah.util.UID; see Listing 2).
- Create native C module to implement the methods declared in the Java code (see Listings 3, 4).
- Compile the native code.
- 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 {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.
System.loadLibrary("uid");
}
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_setuidNotice 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.
(JNIEnv *, jclass, jint);
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>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.
#include "micah_util_UID.h"
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 used the following command to compile the code under Windows NT:
-I/usr/local/java/include \
-I/usr/local/java/include/genunix \
-shared unix_uid.c -o libuid.so
cl -Ie:\jdk1.1.8\include -Ie:\jdk1.1.8\include\win32Create a Test Java App
-LD win_uid.c -Feuid.dll
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
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.
Published October 1, 2000 Reads 11,034
Copyright © 2000 SYS-CON Media, Inc. — All Rights Reserved.
Syndicated stories and blog feeds, all rights reserved by the author.
Related Links
More Stories By Micah Silverman
Micah Silverman has been working in software development and computer security since the 1980s. He has been developing Java applications since the
language was released in 1995. He is a Sun Certified Java Programmer and an ISC2 CISSP (Certified Information Systems Security Professional).


















