Skip to content

JNI calls continue after a failed GetPrimitiveArrayCritical in BitShuffleNative.shuffle #727

Description

@K-ANOY

I found a possible error-path problem in BitShuffleNative.shuffle: after a
failed GetPrimitiveArrayCritical, the code keeps making JNI calls (including a
Java upcall) without ever checking for a pending exception.

File: src/main/java/org/xerial/snappy/BitShuffleNative.cpp

Function: Java_org_xerial_snappy_BitShuffleNative_shuffle

Relevant code:

char* in = (char*) env->GetPrimitiveArrayCritical((jarray) input, 0);
char* out = (char*) env->GetPrimitiveArrayCritical((jarray) output, 0);
if(in == 0 || out == 0) {
    // out of memory
    if(in != 0) {
        env->ReleasePrimitiveArrayCritical((jarray) input, in, 0);
    }
    if(out != 0) {
        env->ReleasePrimitiveArrayCritical((jarray) output, out, 0);
    }
    throw_exception(env, self, 4);
    return 0;
}

GetPrimitiveArrayCritical() returns NULL on failure and, on some JVMs, can
leave an OutOfMemoryError pending. Two problems follow:

  1. If the first acquisition (input) returns NULL, the second
    GetPrimitiveArrayCritical(output) is still executed — a JNI call made while
    an exception may already be pending.

  2. throw_exception() is then called, and it makes ordinary JNI lookups plus a
    Java method call:

    inline void throw_exception(JNIEnv *env, jobject self, int errorCode) {
        jclass c = env->FindClass("org/xerial/snappy/SnappyNative");
        if(c == 0) return;
        jmethodID mth_throwex = env->GetMethodID(c, "throw_error", "(I)V");
        if(mth_throwex == 0) return;
        env->CallVoidMethod(self, mth_throwex, (jint) errorCode);
    }

Once an exception is pending, native code should only inspect/clear it and do
cleanup, not make normal JNI lookups or a Java upcall (CallVoidMethod). Doing so
can mask the original OutOfMemoryError, raise a second exception over the first,
and is flagged by -Xcheck:jni. The success path (both pointers released) is fine;
the issue is strictly the failure path continuing JNI activity without an
ExceptionCheck().

Suggested fix: check each acquisition immediately, release a successful first
acquisition if the second fails, and only raise a new exception when none is
pending:

char* in = (char*) env->GetPrimitiveArrayCritical((jarray) input, 0);
if(in == 0) {
    if(!env->ExceptionCheck()) throw_exception(env, self, 4);
    return 0;
}
char* out = (char*) env->GetPrimitiveArrayCritical((jarray) output, 0);
if(out == 0) {
    env->ReleasePrimitiveArrayCritical((jarray) input, in, JNI_ABORT);
    if(!env->ExceptionCheck()) throw_exception(env, self, 4);
    return 0;
}

This is low severity and JVM-dependent — the path is only reached when
GetPrimitiveArrayCritical fails, and whether an exception is actually pending
then is not guaranteed by the JNI spec — but it is a clear error-path defect. The
same pattern appears in the other array-variant methods in BitShuffleNative.cpp
and SnappyNative.cpp.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions