quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

commit 201c563e2906b184aab8ea4490289d31fc861c03
parent c9c4c204176a07c1a67d2bad27e20f5853c104d7
Author: Florian Dold <dold@taler.net>
Date:   Sun,  9 Aug 2026 21:07:42 +0200

tart: validate native buffer allocations

Diffstat:
Mtart_module.c | 81++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 72 insertions(+), 9 deletions(-)

diff --git a/tart_module.c b/tart_module.c @@ -35,6 +35,8 @@ #include "sqlite3/sqlite3.h" #endif +static JSValue make_js_ta_copy(JSContext *ctx, uint8_t *data, size_t size); + static JSValue js_encode_utf8(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -42,6 +44,9 @@ static JSValue js_encode_utf8(JSContext *ctx, JSValueConst this_val, size_t len; JSValue buf; str = JS_ToCStringLen2(ctx, &len, argv[0], FALSE); + if (!str) { + return JS_EXCEPTION; + } // FIXME: Don't copy buffer but pass destructor function buf = JS_NewArrayBufferCopy(ctx, (const uint8_t*) str, len); JS_FreeCString(ctx, str); @@ -59,14 +64,23 @@ static JSValue js_random_bytes(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint32_t nbytes; - JSValue buf; + uint8_t *randbuf = NULL; + JSValue buf = JS_EXCEPTION; + if (0 != JS_ToUint32(ctx, &nbytes, argv[0])) { return JS_EXCEPTION; } - { - uint8_t randbuf[nbytes]; + if (nbytes != 0) { + randbuf = js_malloc(ctx, nbytes); + if (!randbuf) { + return JS_EXCEPTION; + } randombytes_buf(randbuf, nbytes); - buf = JS_NewArrayBufferCopy(ctx, randbuf, nbytes); + } + buf = JS_NewArrayBufferCopy(ctx, randbuf, nbytes); + if (randbuf) { + sodium_memzero(randbuf, nbytes); + js_free(ctx, randbuf); } return buf; } @@ -151,7 +165,9 @@ static JSValue js_talercrypto_encode_crock(JSContext *ctx, JSValue this_val, goto exception; } - assert (size < SIZE_MAX / 8 - 4); + if (size > (SIZE_MAX - 4) / 8) { + return JS_ThrowRangeError(ctx, "input is too large"); + } out_size = size * 8; if (out_size % 5 > 0) @@ -159,10 +175,11 @@ static JSValue js_talercrypto_encode_crock(JSContext *ctx, JSValue this_val, out_size /= 5; out = malloc (out_size + 1); - memset (out, 0, out_size + 1); if (!out) { + JS_ThrowOutOfMemory(ctx); goto exception; } + memset (out, 0, out_size + 1); udata = buf; if (out_size < (size * 8 + 4) / 5) { @@ -232,10 +249,21 @@ static JSValue js_talercrypto_decode_crock(JSContext *ctx, JSValue this_val, goto exception; } + if (enclen == 0) { + ret_val = make_js_ta_copy(ctx, NULL, 0); + goto done; + } + if (enclen > SIZE_MAX / 5) { + JS_ThrowRangeError(ctx, "encoded input is too large"); + goto exception; + } out_size = (enclen * 5) / 8; encoded_len = out_size * 8; uout = malloc(out_size); - assert (out_size < SIZE_MAX / 8); + if (out_size != 0 && !uout) { + JS_ThrowOutOfMemory(ctx); + goto exception; + } wpos = out_size; rpos = enclen; if ((encoded_len % 5) > 0) @@ -267,6 +295,7 @@ static JSValue js_talercrypto_decode_crock(JSContext *ctx, JSValue this_val, } bits = ((ret = getValue__ (enc[--rpos])) << vbit) | bits; if (-1 == ret) { + JS_ThrowTypeError(ctx, "invalid character in encoding"); goto exception; } vbit += 5; @@ -514,14 +543,32 @@ static JSValue js_talercrypto_hash_argon2id(JSContext *ctx, JSValue this_val, goto exception; } + if (iters < crypto_pwhash_OPSLIMIT_MIN || + iters > crypto_pwhash_OPSLIMIT_MAX) { + JS_ThrowRangeError(ctx, "iterations out of range"); + goto exception; + } + if (hash_len < crypto_pwhash_BYTES_MIN || + hash_len > crypto_pwhash_BYTES_MAX) { + JS_ThrowRangeError(ctx, "hash length out of range"); + goto exception; + } + // Check for overflow when converting memory size to bytes if (((unsigned long long)mem_size * 1024) > UINT32_MAX) { JS_ThrowTypeError(ctx, "mem_size too large"); goto exception; } + if ((size_t) mem_size * 1024 < crypto_pwhash_MEMLIMIT_MIN || + (size_t) mem_size * 1024 > crypto_pwhash_MEMLIMIT_MAX) { + JS_ThrowRangeError(ctx, "memory size out of range"); + goto exception; + } + hash = malloc(hash_len); if (NULL == hash) { + JS_ThrowOutOfMemory(ctx); goto exception; } @@ -727,6 +774,7 @@ static JSValue js_talercrypto_kdf(JSContext *ctx, JSValue this_val, uint8_t *ikm; uint8_t *info; uint8_t *okm = NULL; + uint8_t empty_okm; uint32_t out_bytes; JSValue ret_val; int ret; @@ -735,6 +783,10 @@ static JSValue js_talercrypto_kdf(JSContext *ctx, JSValue this_val, goto exception; } okm_len = out_bytes; + if (okm_len > 255 * 32) { + JS_ThrowRangeError(ctx, "HKDF output is too large"); + goto exception; + } ikm = JS_GetArrayBuffer(ctx, &ikm_len, argv[1]); if (!ikm) { @@ -761,9 +813,16 @@ static JSValue js_talercrypto_kdf(JSContext *ctx, JSValue this_val, } } - okm = malloc(okm_len); + if (okm_len != 0) { + okm = malloc(okm_len); + if (!okm) { + JS_ThrowOutOfMemory(ctx); + goto exception; + } + } - ret = kdf(okm, okm_len, ikm, ikm_len, salt, salt_len, info, info_len); + ret = kdf(okm_len == 0 ? &empty_okm : okm, okm_len, + ikm, ikm_len, salt, salt_len, info, info_len); if (ret != 0) { JS_ThrowInternalError(ctx, "kdf() call failed"); @@ -1390,6 +1449,10 @@ static JSValue js_talercrypto_hash_state_update(JSContext *ctx, JSValue this_val data = JS_GetArrayBuffer(ctx, &data_len, data_val); + if (!data) { + return JS_EXCEPTION; + } + if (0 != crypto_hash_sha512_update(&hstate->h, data, data_len)) { return JS_ThrowInternalError(ctx, "hashing failed"); }