Skip to content

Fix native crypto memory leaks - #209

Merged
binsky08 merged 7 commits into
masterfrom
fix/native-memory-leaks
Oct 31, 2026
Merged

binsky08 merged 7 commits into
masterfrom
fix/native-memory-leaks

Conversation

@binsky08

@binsky08 binsky08 commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

The native SJCL crypto leaked memory on every encryption and decryption. Some of the leaked buffers held passwords, derived keys and decrypted plaintext, so these secrets stayed in the native heap.

  • Java strings are copied with GetStringUTFRegion instead of GetStringUTFChars. The JNI copies were never released, and they held the vault password and the plaintext. The password and plaintext are now wiped after use, and SJCL::decrypt takes the password by const reference instead of copying it.
  • The JNI functions now fully release the base64 results. Before, only the struct was freed (with free, although it was allocated with new) and its data leaked. The encrypted result was never freed at all.
  • Decryption now releases the cryptogram, salt, IV, the parsed JSON tree and the plaintext buffer. The plaintext and the returned base64 plaintext are wiped before they're freed.
  • Encryption now releases the ciphertext with its tag and the three base64 buffers.
  • The derived AES keys are wiped with OPENSSL_cleanse before they're freed, in both directions.
  • New instrumented test NativeCryptoMemoryTest runs 1000 encryptions and 1000 decryptions of a 2 KB value with non-ASCII characters and checks the native heap growth.
    • Before the fix: about 40 MB growth for decryption and 48 MB for encryption per 1000 runs.
    • After the fix: no measurable growth, on both API 37 and API 23.

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI (claude opus 5.5; basically for findings and tests)

…ipe them after usage

GetStringUTFChars() was never paired with ReleaseStringUTFChars(), every call leaked a copy of the vault password and of the plaintext to encrypt.
GetStringUTFRegion() writes into our own buffer instead, which is wiped after use.
SJCL::decrypt() takes its arguments by reference, so there are no further copies of the password.

Signed-off-by: binsky <timo@binsky.org>
The base64 results are allocated with new and their data with malloc, but only the struct was released with free(). The result of SJCL::encrypt() was never released.

Signed-off-by: binsky <timo@binsky.org>
The decrypted plaintext stayed in the native heap, as well as the decoded ciphertext, salt and IV. The parsed JSON was released with free() instead of delete, which also leaked its values in the memory.

Signed-off-by: binsky <timo@binsky.org>
Encrypts and decrypts a value 1000 times each and checks that the native heap doesn't grow.
Before the fixes, the heap grew by about 40 KB per decryption and 48 KB per encryption of a 2 KB value.

Signed-off-by: binsky <timo@binsky.org>
Signed-off-by: binsky <timo@binsky.org>
@binsky08 binsky08 changed the title Fix native memory leaks Fix native crypto memory leaks Oct 31, 2026
@binsky08
binsky08 merged commit b06c460 into master Oct 31, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant