diff --git a/.changeset/red-deers-notice.md b/.changeset/red-deers-notice.md new file mode 100644 index 0000000..6625f89 --- /dev/null +++ b/.changeset/red-deers-notice.md @@ -0,0 +1,6 @@ +--- +"@openwallet-foundation/askar-react-native": patch +"@openwallet-foundation/askar-nodejs": patch +--- + +fix: memory leak from not freeing secret and encrypted buffers diff --git a/packages/askar-nodejs/src/NodeJSAskar.ts b/packages/askar-nodejs/src/NodeJSAskar.ts index 71bb1d8..1ab24b6 100644 --- a/packages/askar-nodejs/src/NodeJSAskar.ts +++ b/packages/askar-nodejs/src/NodeJSAskar.ts @@ -91,14 +91,9 @@ import { StoreHandle, handleInvalidNullResponse, } from '@openwallet-foundation/askar-shared' -import type { - ByteBufferType, - EncryptedBufferType, - NativeCallback, - NativeCallbackWithResponse, - SecretBufferType, -} from './ffi' import { + type ByteBufferType, + type EncryptedBufferType, FFI_ENTRY_LIST_HANDLE, FFI_INT8, FFI_INT64, @@ -108,6 +103,8 @@ import { FFI_STORE_HANDLE, FFI_STRING, FFI_STRING_LIST_HANDLE, + type NativeCallback, + type NativeCallbackWithResponse, allocateAeadParams, allocateEncryptedBuffer, allocateInt8Buffer, @@ -319,9 +316,11 @@ export class NodeJSAskar implements Askar { const errorCode = this.nativeAskar.askar_entry_list_get_value(entryListHandle, index, ret) this.handleError(errorCode) - const byteBuffer = handleReturnPointer(ret) - return new Uint8Array(secretBufferToBuffer(byteBuffer)) + const bufferArray = new Uint8Array(Buffer.from(secretBufferToBuffer(byteBuffer))) + this.nativeAskar.askar_buffer_free(byteBuffer) + + return bufferArray } public keyAeadDecrypt(options: KeyAeadDecryptOptions): Uint8Array { @@ -330,9 +329,11 @@ export class NodeJSAskar implements Askar { const errorCode = this.nativeAskar.askar_key_aead_decrypt(localKeyHandle, ciphertext, nonce, tag, aad, ret) this.handleError(errorCode) - const byteBuffer = handleReturnPointer(ret) - return new Uint8Array(secretBufferToBuffer(byteBuffer)) + const bufferArray = new Uint8Array(Buffer.from(secretBufferToBuffer(byteBuffer))) + this.nativeAskar.askar_buffer_free(byteBuffer) + + return bufferArray } public keyAeadEncrypt(options: KeyAeadEncryptOptions): EncryptedBuffer { @@ -341,9 +342,11 @@ export class NodeJSAskar implements Askar { const errorCode = this.nativeAskar.askar_key_aead_encrypt(localKeyHandle, message, nonce, aad, ret) this.handleError(errorCode) - const encryptedBuffer = handleReturnPointer(ret) - return encryptedBufferStructToClass(encryptedBuffer) + const encryptedBufferClass = encryptedBufferStructToClass(encryptedBuffer) + this.nativeAskar.askar_buffer_free(encryptedBuffer.secretBuffer) + + return encryptedBufferClass } public keyAeadGetPadding(options: KeyAeadGetPaddingOptions): number { @@ -372,9 +375,11 @@ export class NodeJSAskar implements Askar { const errorCode = this.nativeAskar.askar_key_aead_random_nonce(localKeyHandle, ret) this.handleError(errorCode) + const byteBuffer = handleReturnPointer(ret) + const bufferArray = new Uint8Array(Buffer.from(secretBufferToBuffer(byteBuffer))) + this.nativeAskar.askar_buffer_free(byteBuffer) - const secretBuffer = handleReturnPointer(ret) - return new Uint8Array(secretBufferToBuffer(secretBuffer)) + return bufferArray } public keyConvert(options: KeyConvertOptions): LocalKeyHandle { @@ -394,9 +399,11 @@ export class NodeJSAskar implements Askar { const errorCode = this.nativeAskar.askar_key_crypto_box(recipientKey, senderKey, message, nonce, ret) this.handleError(errorCode) + const byteBuffer = handleReturnPointer(ret) + const bufferArray = new Uint8Array(Buffer.from(secretBufferToBuffer(byteBuffer))) + this.nativeAskar.askar_buffer_free(byteBuffer) - const secretBuffer = handleReturnPointer(ret) - return new Uint8Array(secretBufferToBuffer(secretBuffer)) + return bufferArray } public keyCryptoBoxOpen(options: KeyCryptoBoxOpenOptions): Uint8Array { @@ -405,9 +412,11 @@ export class NodeJSAskar implements Askar { const errorCode = this.nativeAskar.askar_key_crypto_box_open(recipientKey, senderKey, message, nonce, ret) this.handleError(errorCode) + const byteBuffer = handleReturnPointer(ret) + const bufferArray = new Uint8Array(Buffer.from(secretBufferToBuffer(byteBuffer))) + this.nativeAskar.askar_buffer_free(byteBuffer) - const secretBuffer = handleReturnPointer(ret) - return new Uint8Array(secretBufferToBuffer(secretBuffer)) + return bufferArray } public keyCryptoBoxRandomNonce(): Uint8Array { @@ -415,9 +424,11 @@ export class NodeJSAskar implements Askar { const errorCode = this.nativeAskar.askar_key_crypto_box_random_nonce(ret) this.handleError(errorCode) + const byteBuffer = handleReturnPointer(ret) + const bufferArray = new Uint8Array(Buffer.from(secretBufferToBuffer(byteBuffer))) + this.nativeAskar.askar_buffer_free(byteBuffer) - const secretBuffer = handleReturnPointer(ret) - return new Uint8Array(secretBufferToBuffer(secretBuffer)) + return bufferArray } public keyCryptoBoxSeal(options: KeyCryptoBoxSealOptions): Uint8Array { @@ -426,9 +437,11 @@ export class NodeJSAskar implements Askar { const errorCode = this.nativeAskar.askar_key_crypto_box_seal(localKeyHandle, message, ret) this.handleError(errorCode) + const byteBuffer = handleReturnPointer(ret) + const bufferArray = new Uint8Array(Buffer.from(secretBufferToBuffer(byteBuffer))) + this.nativeAskar.askar_buffer_free(byteBuffer) - const secretBuffer = handleReturnPointer(ret) - return new Uint8Array(secretBufferToBuffer(secretBuffer)) + return bufferArray } public keyCryptoBoxSealOpen(options: KeyCryptoBoxSealOpenOptions): Uint8Array { @@ -437,9 +450,11 @@ export class NodeJSAskar implements Askar { const errorCode = this.nativeAskar.askar_key_crypto_box_seal_open(localKeyHandle, ciphertext, ret) this.handleError(errorCode) + const byteBuffer = handleReturnPointer(ret) + const bufferArray = new Uint8Array(Buffer.from(secretBufferToBuffer(byteBuffer))) + this.nativeAskar.askar_buffer_free(byteBuffer) - const secretBuffer = handleReturnPointer(ret) - return new Uint8Array(secretBufferToBuffer(secretBuffer)) + return bufferArray } public keyDeriveEcdh1pu(options: KeyDeriveEcdh1puOptions): LocalKeyHandle { @@ -661,9 +676,11 @@ export class NodeJSAskar implements Askar { const errorCode = this.nativeAskar.askar_key_get_jwk_secret(localKeyHandle, ret) this.handleError(errorCode) + const byteBuffer = handleReturnPointer(ret) + const bufferArray = new Uint8Array(Buffer.from(secretBufferToBuffer(byteBuffer))) + this.nativeAskar.askar_buffer_free(byteBuffer) - const secretBuffer = handleReturnPointer(ret) - return new Uint8Array(secretBufferToBuffer(secretBuffer)) + return bufferArray } public keyGetJwkThumbprint(options: KeyGetJwkThumbprintOptions): string { @@ -682,9 +699,11 @@ export class NodeJSAskar implements Askar { const errorCode = this.nativeAskar.askar_key_get_public_bytes(localKeyHandle, ret) this.handleError(errorCode) + const byteBuffer = handleReturnPointer(ret) + const bufferArray = new Uint8Array(Buffer.from(secretBufferToBuffer(byteBuffer))) + this.nativeAskar.askar_buffer_free(byteBuffer) - const secretBuffer = handleReturnPointer(ret) - return new Uint8Array(secretBufferToBuffer(secretBuffer)) + return bufferArray } public keyGetSecretBytes(options: KeyGetSecretBytesOptions): Uint8Array { @@ -693,9 +712,11 @@ export class NodeJSAskar implements Askar { const errorCode = this.nativeAskar.askar_key_get_secret_bytes(localKeyHandle, ret) this.handleError(errorCode) + const byteBuffer = handleReturnPointer(ret) + const bufferArray = new Uint8Array(Buffer.from(secretBufferToBuffer(byteBuffer))) + this.nativeAskar.askar_buffer_free(byteBuffer) - const secretBuffer = handleReturnPointer(ret) - return new Uint8Array(secretBufferToBuffer(secretBuffer)) + return bufferArray } public keySignMessage(options: KeySignMessageOptions): Uint8Array { @@ -704,9 +725,11 @@ export class NodeJSAskar implements Askar { const errorCode = this.nativeAskar.askar_key_sign_message(localKeyHandle, message, sigType, ret) this.handleError(errorCode) + const byteBuffer = handleReturnPointer(ret) + const bufferArray = new Uint8Array(Buffer.from(secretBufferToBuffer(byteBuffer))) + this.nativeAskar.askar_buffer_free(byteBuffer) - const secretBuffer = handleReturnPointer(ret) - return new Uint8Array(secretBufferToBuffer(secretBuffer)) + return bufferArray } public keyUnwrapKey(options: KeyUnwrapKeyOptions): LocalKeyHandle { @@ -736,9 +759,11 @@ export class NodeJSAskar implements Askar { const errorCode = this.nativeAskar.askar_key_wrap_key(localKeyHandle, other, nonce, ret) this.handleError(errorCode) - const encryptedBuffer = handleReturnPointer(ret) - return encryptedBufferStructToClass(encryptedBuffer) + const encryptedBufferClass = encryptedBufferStructToClass(encryptedBuffer) + this.nativeAskar.askar_buffer_free(encryptedBuffer.secretBuffer) + + return encryptedBufferClass } public keyGetSupportedBackends(): string[] { diff --git a/packages/askar-nodejs/src/library/NativeBindingInterface.ts b/packages/askar-nodejs/src/library/NativeBindingInterface.ts index bb0a9fa..794e3b2 100644 --- a/packages/askar-nodejs/src/library/NativeBindingInterface.ts +++ b/packages/askar-nodejs/src/library/NativeBindingInterface.ts @@ -1,5 +1,6 @@ import type { nativeBindings } from './bindings' +import type { ByteBufferType, SecretBufferStruct } from '../ffi' // We need a mapping from string type value => type (property 'string' maps to type string) interface StringTypeMapping { pointer: Buffer @@ -17,7 +18,11 @@ type ShapeOf = { [Property in keyof T]: T[Property] } type StringTypeArrayToTypes = { - [Item in keyof List]: List[Item] extends keyof StringTypeMapping ? StringTypeMapping[List[Item]] : Buffer + [Item in keyof List]: List[Item] extends keyof StringTypeMapping + ? StringTypeMapping[List[Item]] + : List[Item] extends Mutable + ? ByteBufferType | Buffer + : Buffer } // biome-ignore lint/suspicious/noExplicitAny: diff --git a/packages/askar-nodejs/tests/indy_wallet_sqlite_upgraded.db b/packages/askar-nodejs/tests/indy_wallet_sqlite_upgraded.db index bd45422..4754d82 100644 Binary files a/packages/askar-nodejs/tests/indy_wallet_sqlite_upgraded.db and b/packages/askar-nodejs/tests/indy_wallet_sqlite_upgraded.db differ diff --git a/packages/askar-react-native/cpp/askar.cpp b/packages/askar-react-native/cpp/askar.cpp index f823c33..6b6cf20 100644 --- a/packages/askar-react-native/cpp/askar.cpp +++ b/packages/askar-react-native/cpp/askar.cpp @@ -75,7 +75,9 @@ jsi::Value entryListGetValue(jsi::Runtime &rt, jsi::Object options) { ErrorCode code = askar_entry_list_get_value(entryListHandle, index, &out); - return createReturnValue(rt, code, &out); + auto ret = createReturnValue(rt, code, &out); + askar_buffer_free(out); + return ret; } jsi::Value entryListGetName(jsi::Runtime &rt, jsi::Object options) { @@ -721,7 +723,9 @@ jsi::Value keyGetJwkSecret(jsi::Runtime &rt, jsi::Object options) { ErrorCode code = askar_key_get_jwk_secret(localKeyHandle, &out); - return createReturnValue(rt, code, &out); + auto ret = createReturnValue(rt, code, &out); + askar_buffer_free(out); + return ret; } jsi::Value keyGetJwkThumbprint(jsi::Runtime &rt, jsi::Object options) { @@ -745,7 +749,9 @@ jsi::Value keyGetPublicBytes(jsi::Runtime &rt, jsi::Object options) { ErrorCode code = askar_key_get_public_bytes(localKeyHandle, &out); - return createReturnValue(rt, code, &out); + auto ret = createReturnValue(rt, code, &out); + askar_buffer_free(out); + return ret; } jsi::Value keyGetSecretBytes(jsi::Runtime &rt, jsi::Object options) { @@ -756,7 +762,9 @@ jsi::Value keyGetSecretBytes(jsi::Runtime &rt, jsi::Object options) { ErrorCode code = askar_key_get_secret_bytes(localKeyHandle, &out); - return createReturnValue(rt, code, &out); + auto ret = createReturnValue(rt, code, &out); + askar_buffer_free(out); + return ret; } jsi::Value keySignMessage(jsi::Runtime &rt, jsi::Object options) { @@ -772,7 +780,9 @@ jsi::Value keySignMessage(jsi::Runtime &rt, jsi::Object options) { localKeyHandle, message, sigType.length() ? sigType.c_str() : nullptr, &out); - return createReturnValue(rt, code, &out); + auto ret = createReturnValue(rt, code, &out); + askar_buffer_free(out); + return ret; } jsi::Value keyUnwrapKey(jsi::Runtime &rt, jsi::Object options) { @@ -820,7 +830,9 @@ jsi::Value keyWrapKey(jsi::Runtime &rt, jsi::Object options) { ErrorCode code = askar_key_wrap_key(localKeyHandle, other, nonce, &out); - return createReturnValue(rt, code, &out); + auto ret = createReturnValue(rt, code, &out); + askar_buffer_free(out.buffer); + return ret; } jsi::Value keyConvert(jsi::Runtime &rt, jsi::Object options) { @@ -855,7 +867,9 @@ jsi::Value keyCryptoBox(jsi::Runtime &rt, jsi::Object options) { ErrorCode code = askar_key_crypto_box(recipientKey, senderKey, message, nonce, &out); - return createReturnValue(rt, code, &out); + auto ret = createReturnValue(rt, code, &out); + askar_buffer_free(out); + return ret; } jsi::Value keyCryptoBoxOpen(jsi::Runtime &rt, jsi::Object options) { @@ -869,7 +883,9 @@ jsi::Value keyCryptoBoxOpen(jsi::Runtime &rt, jsi::Object options) { ErrorCode code = askar_key_crypto_box_open(recipientKey, senderKey, message, nonce, &out); - return createReturnValue(rt, code, &out); + auto ret = createReturnValue(rt, code, &out); + askar_buffer_free(out); + return ret; } jsi::Value keyCryptoBoxRandomNonce(jsi::Runtime &rt, jsi::Object options) { @@ -877,7 +893,9 @@ jsi::Value keyCryptoBoxRandomNonce(jsi::Runtime &rt, jsi::Object options) { ErrorCode code = askar_key_crypto_box_random_nonce(&out); - return createReturnValue(rt, code, &out); + auto ret = createReturnValue(rt, code, &out); + askar_buffer_free(out); + return ret; } jsi::Value keyCryptoBoxSeal(jsi::Runtime &rt, jsi::Object options) { @@ -889,7 +907,9 @@ jsi::Value keyCryptoBoxSeal(jsi::Runtime &rt, jsi::Object options) { ErrorCode code = askar_key_crypto_box_seal(localKeyHandle, message, &out); - return createReturnValue(rt, code, &out); + auto ret = createReturnValue(rt, code, &out); + askar_buffer_free(out); + return ret; } jsi::Value keyCryptoBoxSealOpen(jsi::Runtime &rt, jsi::Object options) { @@ -902,7 +922,9 @@ jsi::Value keyCryptoBoxSealOpen(jsi::Runtime &rt, jsi::Object options) { ErrorCode code = askar_key_crypto_box_seal_open(localKeyHandle, ciphertext, &out); - return createReturnValue(rt, code, &out); + auto ret = createReturnValue(rt, code, &out); + askar_buffer_free(out); + return ret; } jsi::Value keyDeriveEcdh1pu(jsi::Runtime &rt, jsi::Object options) { @@ -958,7 +980,9 @@ jsi::Value keyAeadDecrypt(jsi::Runtime &rt, jsi::Object options) { ErrorCode code = askar_key_aead_decrypt(localKeyHandle, ciphertext, nonce, tag, aad, &out); - return createReturnValue(rt, code, &out); + auto ret = createReturnValue(rt, code, &out); + askar_buffer_free(out); + return ret; } jsi::Value keyAeadEncrypt(jsi::Runtime &rt, jsi::Object options) { @@ -974,7 +998,9 @@ jsi::Value keyAeadEncrypt(jsi::Runtime &rt, jsi::Object options) { ErrorCode code = askar_key_aead_encrypt(localKeyHandle, message, nonce, aad, &out); - return createReturnValue(rt, code, &out); + auto ret = createReturnValue(rt, code, &out); + askar_buffer_free(out.buffer); + return ret; } jsi::Value keyAeadGetPadding(jsi::Runtime &rt, jsi::Object options) { @@ -1009,7 +1035,9 @@ jsi::Value keyAeadRandomNonce(jsi::Runtime &rt, jsi::Object options) { ErrorCode code = askar_key_aead_random_nonce(localKeyHandle, &out); - return createReturnValue(rt, code, &out); + auto ret = createReturnValue(rt, code, &out); + askar_buffer_free(out); + return ret; } jsi::Value keyEntryListCount(jsi::Runtime &rt, jsi::Object options) {