Skip to content

Byte-array elements leak in EVP_DigestSign() #1519

Description

@K-ANOY

I found a possible JNI array leak in NativeCrypto_EVP_DigestSign(). The input elements acquired with GetByteArrayElements() are never released.

File: common/src/jni/main/cpp/conscrypt/native_crypto.cc

Function: NativeCrypto_EVP_DigestSign

Relevant code:

jbyte* array_elements = env->GetByteArrayElements(inJavaBytes, nullptr);
if (array_elements == nullptr) {
    conscrypt::jniutil::throwOutOfMemory(env, "Unable to obtain elements of inBytes");
    return nullptr;
}
const unsigned char* buf = reinterpret_cast<const unsigned char*>(array_elements);
const unsigned char* inStart = buf + in_offset;
size_t inLen = static_cast<size_t>(in_size);

size_t maxLen;
if (EVP_DigestSign(mdCtx, nullptr, &maxLen, inStart, inLen) != 1) {
    conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSign");
    return nullptr;
}

std::unique_ptr<unsigned char[]> buffer(new unsigned char[maxLen]);
size_t actualLen(maxLen);
if (EVP_DigestSign(mdCtx, buffer.get(), &actualLen, inStart, inLen) != 1) {
    conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSign");
    return nullptr;
}
/* ... */
env->SetByteArrayRegion(sigJavaBytes.get(), 0, static_cast<jint>(actualLen),
                        reinterpret_cast<jbyte*>(buffer.get()));
return sigJavaBytes.release();

GetByteArrayElements() returns a pointer that must be paired with
ReleaseByteArrayElements(), but there is no such call anywhere between the
acquisition and any return from the function. Every error return (the two
EVP_DigestSign() failures, the actualLen > maxLen check, and the
NewByteArray() failure) leaks it, and so does the normal success path at
return sigJavaBytes.release(). Returning from the native method does not
release the elements.

The function only reads the input, so the correct release mode is JNI_ABORT.
The project already provides ScopedByteArrayRO, whose destructor calls
ReleaseByteArrayElements(..., JNI_ABORT), and most comparable functions in
native_crypto.cc use it. The raw acquisition here is inconsistent with that
convention.

The method backs the ordinary EdDSA and ML-DSA Signature.sign() paths
(OpenSslSignatureEdDsa.engineSign() / OpenSslSignatureMlDsa.engineSign()),
so repeated signing repeatedly reaches the leaking success path with ordinary
valid input.

Suggested fix: acquire the input through ScopedByteArrayRO (or call
ReleaseByteArrayElements(inJavaBytes, array_elements, JNI_ABORT) before every
return). RAII is preferable given the several error exits.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions