class Vulnerable { //Declare a native method private native void bcopy(byte[] arr); public void byteCopy(byte[] arr) { //Call the native method bcopy(arr); } static { System.loadLibrary("Vulnerable"); } } |
#include <jni.h>
#include "Vulnerable.h"
JNIEXPORT void JNICALL Java_Vulnerable_bcopy
(JNIEnv *env, jobject obj, jobject arr)
{
char buffer[512];
jbyte *carr;
carr = (*env)->GetByteArrayElements(env,arr,0);
//Unbounded string copy to a local buffer
strcpy(buffer, carr);
(*env)->ReleaseByteArrayElements(env,arr,carr,0);
}
|
|
void Java_Vulnerable_bcopy (JNIEnv *env, jobject obj, jbyteArray jarr) { char buffer[512]; if ((*env)->GetArrayLength(env, jarr) > 512) { JNU_ThrowArrayIndexOutOfBoundsException(env, 0); } //Get a pointer to the Java array, //then copy the Java array to a local buffer jbyte *carr = (*env)->GetByteArrayElements(env, jarr, NULL); strcpy(buffer, carr); (*env)->ReleaseByteArrayElements(env,arr,carr,0); } |
static void* xmalloc(JNIEnv *env, size_t size) { void *p = malloc(size); if (p == NULL) JNU_ThrowOutOfMemoryError(env, NULL); return p; } #define NEW(type, n) ((type *) xmalloc(env, (n) * sizeof(type))) static const char * const * splitPath(JNIEnv *env, const char *path) { ... pathv = NEW(char*, count+1); pathv[count] = NULL; ... } |
typedef struct z_stream_s {...} z_stream; jlong Java_java_util_zip_Deflater_init ( ... ) { z_stream *strm = calloc(1, sizeof(z_stream)); ... //initialize strm return (jlong) strm; //cast it to an integer } |
class GUILib { public native static void setFocus (int windowPtr); ... } |
jint Java_java_util_zip_Deflater_getAdler (..., jlong strm) { return ((z_stream *)strm)->adler; } |
//Get the field ID fid=(*env)->GetFieldID(env, cls, "x", "I"); //Get the int field int i=(*env)->GetIntField(env, obj, fid); |
JNI API FUNCTIONS | # OF VIOLATIONS |
GetFieldID/GetStaticFieldID | 5 |
GetMethodID/GetStaticMethodID | 3 |
GetStringUTFChars | 4 |
FindClass | 11 |
New‡Type–Array | 1 |
NewGlobalRef | 11 |
Total | 35 |
Off-the-Shelf Tools | Warnings | Errors | FP rates |
ITS4 -c1 | 241 | 6 | 97.5% |
Flawfinder | 297 | 5 | 98.3% |
Splint5 | 3532 | 7 | 99.8% |
BUG PATTERNS | OUR TOOLS | WARNINGS | ERRORS | FP RATES |
Unexpected control flows due to mishandling exceptions | grep-based scripts | 337 | 11 | 96.7% |
C pointers as Java integers | scanner built in CIL | 46 | 38 | 17.4% |
Mem. management flaws (Java Mem.) | grep-based scripts | 43 | 28 | 34.9% |
Insufficient error checking (JNI APIs) | grep-based scripts | 230 | 35 | 84.8% |
Due to the large number of false positives, we had to manually sift through many cases—the principal reason why we examined only a portion of the native code in the JDK. In addition to false positives, static analysis tools may have false negatives. For example, of the four buffer-overflow bugs identified in the target directories, ITS4 and Flawfinder missed one and Splint missed two.
Another limitation of the static analysis tools is that they analyze C code alone, without considering how the Java side interacts with the C side. This is a severe limitation because the interface code between Java classes and C libraries is where most bugs arise. In fact, all the bugs we identified are in the interface code. This is not only because the two libraries in the target directories (namely, Zlib and fdlibm) have been used in many other applications besides the JDK and are mature, but because programmers tend to make wrong assumptions of the Java and C sides when writing interface code. When analyzing the interface code, considering both sides of Java and C can significantly increase analysis precision and reduce false positives and negatives. To illustrate, we use the java.util.zip.Deflater class as an example. The public deflate method shown in Figure 4 accepts a buffer, an offset, and a length from users, and then invokes the native method deflateBytes. To be safe, the deflate method checks the bounds of the offset and the length parameters before invoking the native method deflateBytes. For the example in Figure 4, a static analysis that analyzes only C code has to make either an optimistic or a pessimistic assumption about whether the Java side has performed the bounds checking. If the analysis makes the optimistic assumption, it would produce false negatives if the Java side had forgotten to check the bounds. If it makes the pessimistic assumption, it would have to flag any access to the b buffer through the offset and the length as a possible error. For example, the SetByteArrayRegion operation in deflateBytes would be flagged as a possible out-of-bounds array write, even though that is impossible given the Java context. Bug finders usually make pessimistic assumptions for the purpose of minimizing false negatives. For instance, Splint flags "malloc(len)" in deflateBytes and complains about an incompatible type cast from the signed integer len to an unsigned integer expected by malloc—it does not know that the Java side invokes deflateBytes only with positive lengths.public class Deflater { public synchronized int deflate(byte[] b, int off, int len) { ... if (off < 0 - - len < 0 - - off > b.length - len) { throw new ArrayIndexOutOfBoundsException(); } return deflateBytes(b, off, len); } private native int deflateBytes(byte[] b, int off, int len); } |
jint Java_java_util_zip_Deflater_deflateBytes (JNIEnv *env, jobject this, jarray b, jint off, jint len) { ... out_buf = (jbyte *) malloc(len); ... (*env)->SetByteArrayRegion(env, b, off, len - strm->avail_out, out_buf); ... } |
BUG PATTERNS | HOW SAFEJNI WORKS AGAINST THE BUGS? | |
Unexpected control flows due to mishandling exceptions | Through SafeJNI's dynamic checks on pending exceptions. | |
Race conditions in file accesses | N/A | |
Buffer overflows | Through CCured and SafeJNI's static pointer kind system. | |
Mem. management flaws | C mem. | Through CCured. |
Java mem. | Through SafeJNI's memory management scheme. | |
Insufficient error checking | JNI APIs | Through SafeJNI's dynamic checks. |
misc. | Through CCured. | |
Type misuses | Through SafeJNI's dynamic checks. | |
Deadlocks | N/A | |
Violating the Java security model | Partly addressed through SafeJNI's dynamic checks on access-control rules on Java fields/methods. |