Skip to content

[BUG] JNI_COMMIT in ReleaseByteArrayElements leaks native buffers with crypto key material (liboqs) #429

Description

@Fuyugithub

Bug Description

In the liboqs JNI bindings, ReleaseByteArrayElements is called with JNI_COMMIT mode in multiple crypto functions. Per the JNI specification, JNI_COMMIT copies modified data back to the Java array but does not free the native buffer. Without a subsequent call using mode 0 or JNI_ABORT, the native buffer is permanently leaked.

Location

liboqs-android/jni/jni/KeyEncapsulation.c, lines 108-109, 132-133, 153
liboqs-android/jni/jni/Signature.c, lines 104-105, 143

Code

// KeyEncapsulation.c generate_keypair (lines 108-109):
(*env)->ReleaseByteArrayElements(env, jpublic_key, public_key_native, JNI_COMMIT);
(*env)->ReleaseByteArrayElements(env, jsecret_key, secret_key_native, JNI_COMMIT);

// KeyEncapsulation.c encap_secret (lines 132-133):
(*env)->ReleaseByteArrayElements(env, jciphertext, ciphertext, JNI_COMMIT);
(*env)->ReleaseByteArrayElements(env, jshared_secret, shared_secret, JNI_COMMIT);

// KeyEncapsulation.c decap_secret (line 153):
(*env)->ReleaseByteArrayElements(env, jshared_secret, shared_secret_native, JNI_COMMIT);

Same pattern in Signature.c for generate_keypair and sign.

Context

From the JNI specification:

Mode Copy back? Free buffer?
0 yes yes
JNI_COMMIT yes no
JNI_ABORT no yes

JNI_COMMIT is intended for partial-update scenarios where you need to copy back intermediate results but continue using the native buffer. It must be followed by a 0 or JNI_ABORT call to actually free the buffer. None of these functions make a second Release call.

Impact

  1. Memory leak: Every call to generate_keypair, encap_secret, decap_secret, or sign permanently leaks native buffers. For key generation, this is ~2-4KB per call (public + secret key). For encapsulation, shared secret + ciphertext leak.

  2. Crypto key material in memory: The leaked buffers contain secret keys, shared secrets, and signatures. Since the native buffer is never freed, OQS_MEM_cleanse or equivalent zeroing never runs on this memory. The sensitive material remains in native heap indefinitely, increasing the window for memory disclosure attacks.

Suggested Fix

Change JNI_COMMIT to 0 for all output buffers:

(*env)->ReleaseByteArrayElements(env, jpublic_key, public_key_native, 0);
(*env)->ReleaseByteArrayElements(env, jsecret_key, secret_key_native, 0);

For secret key buffers, also zero before release:

OQS_MEM_cleanse(secret_key_native, kem->length_secret_key);
(*env)->ReleaseByteArrayElements(env, jsecret_key, secret_key_native, 0);

Environment

  • IVPN Android: current main branch
  • Files: liboqs-android JNI bindings (KeyEncapsulation.c, Signature.c)
  • Affects: post-quantum KEM (Kyber, etc.) and signature (Dilithium, etc.) operations

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