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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions