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.
I found a possible JNI array leak in
NativeCrypto_EVP_DigestSign(). The input elements acquired withGetByteArrayElements()are never released.File:
common/src/jni/main/cpp/conscrypt/native_crypto.ccFunction:
NativeCrypto_EVP_DigestSignRelevant code:
GetByteArrayElements()returns a pointer that must be paired withReleaseByteArrayElements(), but there is no such call anywhere between theacquisition and any return from the function. Every error return (the two
EVP_DigestSign()failures, theactualLen > maxLencheck, and theNewByteArray()failure) leaks it, and so does the normal success path atreturn sigJavaBytes.release(). Returning from the native method does notrelease the elements.
The function only reads the input, so the correct release mode is
JNI_ABORT.The project already provides
ScopedByteArrayRO, whose destructor callsReleaseByteArrayElements(..., JNI_ABORT), and most comparable functions innative_crypto.ccuse it. The raw acquisition here is inconsistent with thatconvention.
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 callReleaseByteArrayElements(inJavaBytes, array_elements, JNI_ABORT)before everyreturn). RAII is preferable given the several error exits.