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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions