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
-
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.
-
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
Bug Description
In the liboqs JNI bindings,
ReleaseByteArrayElementsis called withJNI_COMMITmode in multiple crypto functions. Per the JNI specification,JNI_COMMITcopies modified data back to the Java array but does not free the native buffer. Without a subsequent call using mode0orJNI_ABORT, the native buffer is permanently leaked.Location
liboqs-android/jni/jni/KeyEncapsulation.c, lines 108-109, 132-133, 153liboqs-android/jni/jni/Signature.c, lines 104-105, 143Code
Same pattern in
Signature.cforgenerate_keypairandsign.Context
From the JNI specification:
JNI_COMMITis intended for partial-update scenarios where you need to copy back intermediate results but continue using the native buffer. It must be followed by a0orJNI_ABORTcall to actually free the buffer. None of these functions make a second Release call.Impact
Memory leak: Every call to
generate_keypair,encap_secret,decap_secret, orsignpermanently leaks native buffers. For key generation, this is ~2-4KB per call (public + secret key). For encapsulation, shared secret + ciphertext leak.Crypto key material in memory: The leaked buffers contain secret keys, shared secrets, and signatures. Since the native buffer is never freed,
OQS_MEM_cleanseor 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_COMMITto0for all output buffers:For secret key buffers, also zero before release:
Environment