From c40bbc54080e45b0b5fedd2135633ae71def49d0 Mon Sep 17 00:00:00 2001 From: Ray Ozzie Date: Mon, 9 Mar 2026 21:19:39 -0400 Subject: [PATCH 1/6] fix: very bad memory leaks in low-memory conditions --- n_cjson.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/n_cjson.c b/n_cjson.c index d7432059..8a4e400f 100644 --- a/n_cjson.c +++ b/n_cjson.c @@ -1819,6 +1819,7 @@ NOTE_C_STATIC Jbool _add_item_to_array(J *array, J *item) N_CJSON_PUBLIC(void) JAddItemToArray(J *array, J *item) { if (array == NULL || item == NULL) { + JDelete(item); return; } _add_item_to_array(array, item); @@ -1874,18 +1875,24 @@ NOTE_C_STATIC Jbool _add_item_to_object(J * const object, const char * const str N_CJSON_PUBLIC(void) JAddItemToObject(J *object, const char *string, J *item) { if (object == NULL || string == NULL || item == NULL) { + JDelete(item); return; } - _add_item_to_object(object, string, item, false); + if (!_add_item_to_object(object, string, item, false)) { + JDelete(item); + } } /* Add an item to an object with constant string as key */ N_CJSON_PUBLIC(void) JAddItemToObjectCS(J *object, const char *string, J *item) { if (object == NULL || string == NULL || item == NULL) { + JDelete(item); return; } - _add_item_to_object(object, string, item, true); + if (!_add_item_to_object(object, string, item, true)) { + JDelete(item); + } } N_CJSON_PUBLIC(void) JAddItemReferenceToArray(J *array, J *item) @@ -1901,7 +1908,10 @@ N_CJSON_PUBLIC(void) JAddItemReferenceToObject(J *object, const char *string, J if (object == NULL || string == NULL || item == NULL) { return; } - _add_item_to_object(object, string, _create_reference(item), false); + J *ref = _create_reference(item); + if (!_add_item_to_object(object, string, ref, false)) { + JDelete(ref); + } } N_CJSON_PUBLIC(J*) JAddTrueToObject(J * const object, const char * const name) From 56a56f0baa1bab785587bc4744a4d6c8d4042cd7 Mon Sep 17 00:00:00 2001 From: Ray Ozzie Date: Mon, 9 Mar 2026 22:20:44 -0400 Subject: [PATCH 2/6] Apply suggestion from @zfields Co-authored-by: Zachary J. Fields --- n_cjson.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/n_cjson.c b/n_cjson.c index 8a4e400f..e576764a 100644 --- a/n_cjson.c +++ b/n_cjson.c @@ -1822,7 +1822,9 @@ N_CJSON_PUBLIC(void) JAddItemToArray(J *array, J *item) JDelete(item); return; } - _add_item_to_array(array, item); + if (!_add_item_to_array(array, item)) { + JDelete(item); + } } #if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) From eb4c328238f2535590288b84a7e5db5dbf086711 Mon Sep 17 00:00:00 2001 From: Ray Ozzie Date: Mon, 9 Mar 2026 22:25:54 -0400 Subject: [PATCH 3/6] Apply suggestion from @zfields Co-authored-by: Zachary J. Fields --- n_cjson.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/n_cjson.c b/n_cjson.c index e576764a..99df1371 100644 --- a/n_cjson.c +++ b/n_cjson.c @@ -1902,7 +1902,10 @@ N_CJSON_PUBLIC(void) JAddItemReferenceToArray(J *array, J *item) if (array == NULL || item == NULL) { return; } - _add_item_to_array(array, _create_reference(item)); + J *ref = _create_reference(item); + if (!_add_item_to_array(array, ref)) { + JDelete(ref); + } } N_CJSON_PUBLIC(void) JAddItemReferenceToObject(J *object, const char *string, J *item) From 2db6759d32bc13474b0529210ae499949297d1c1 Mon Sep 17 00:00:00 2001 From: Ray Ozzie Date: Mon, 9 Mar 2026 22:47:37 -0400 Subject: [PATCH 4/6] more leaks and fixes --- n_cjson.c | 36 ++++++++++++++++++++++++++++++------ n_cjson_helpers.c | 2 +- n_hooks.c | 2 +- n_request.c | 1 - 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/n_cjson.c b/n_cjson.c index 99df1371..0e0d44f1 100644 --- a/n_cjson.c +++ b/n_cjson.c @@ -2185,18 +2185,22 @@ N_CJSON_PUBLIC(void) JDeleteItemFromObjectCaseSensitive(J *object, const char *s N_CJSON_PUBLIC(void) JInsertItemInArray(J *array, int which, J *newitem) { if (array == NULL || newitem == NULL) { + JDelete(newitem); return; } J *after_inserted = NULL; if (which < 0) { + JDelete(newitem); return; } after_inserted = _get_array_item(array, (size_t)which); if (after_inserted == NULL) { - _add_item_to_array(array, newitem); + if (!_add_item_to_array(array, newitem)) { + JDelete(newitem); + } return; } @@ -2243,14 +2247,18 @@ N_CJSON_PUBLIC(Jbool) JReplaceItemViaPointer(J * const parent, J * const item, J N_CJSON_PUBLIC(void) JReplaceItemInArray(J *array, int which, J *newitem) { if (array == NULL || newitem == NULL) { + JDelete(newitem); return; } if (which < 0) { + JDelete(newitem); return; } - JReplaceItemViaPointer(array, _get_array_item(array, (size_t)which), newitem); + if (!JReplaceItemViaPointer(array, _get_array_item(array, (size_t)which), newitem)) { + JDelete(newitem); + } } NOTE_C_STATIC Jbool _replace_item_in_object(J *object, const char *string, J *replacement, Jbool case_sensitive) @@ -2259,14 +2267,24 @@ NOTE_C_STATIC Jbool _replace_item_in_object(J *object, const char *string, J *re return false; } + J *existing = _get_object_item(object, string, case_sensitive); + if (existing == NULL) { + return false; + } + + char *new_key = (char*)_j_strdup((const unsigned char*)string); + if (new_key == NULL) { + return false; + } + /* replace the name in the replacement */ if (!(replacement->type & JStringIsConst) && (replacement->string != NULL)) { _Free(replacement->string); } - replacement->string = (char*)_j_strdup((const unsigned char*)string); + replacement->string = new_key; replacement->type &= ~JStringIsConst; - JReplaceItemViaPointer(object, _get_object_item(object, string, case_sensitive), replacement); + JReplaceItemViaPointer(object, existing, replacement); return true; } @@ -2274,17 +2292,23 @@ NOTE_C_STATIC Jbool _replace_item_in_object(J *object, const char *string, J *re N_CJSON_PUBLIC(void) JReplaceItemInObject(J *object, const char *string, J *newitem) { if (object == NULL || newitem == NULL) { + JDelete(newitem); return; } - _replace_item_in_object(object, string, newitem, false); + if (!_replace_item_in_object(object, string, newitem, false)) { + JDelete(newitem); + } } N_CJSON_PUBLIC(void) JReplaceItemInObjectCaseSensitive(J *object, const char *string, J *newitem) { if (object == NULL || newitem == NULL) { + JDelete(newitem); return; } - _replace_item_in_object(object, string, newitem, true); + if (!_replace_item_in_object(object, string, newitem, true)) { + JDelete(newitem); + } } N_CJSON_PUBLIC(J *) JCreateTrue(void) diff --git a/n_cjson_helpers.c b/n_cjson_helpers.c index ebd27a6b..1c744188 100644 --- a/n_cjson_helpers.c +++ b/n_cjson_helpers.c @@ -229,7 +229,7 @@ bool JAddBinaryToObject(J *json, const char *fieldName, const void *binaryData, return false; } JAddItemToObject(json, fieldName, stringItem); - return true; + return JIsPresent(json, fieldName); } bool JGetBinaryFromObject(J *json, const char *fieldName, uint8_t **retBinaryData, uint32_t *retBinaryDataLen) diff --git a/n_hooks.c b/n_hooks.c index d8ee9d40..7b200b99 100644 --- a/n_hooks.c +++ b/n_hooks.c @@ -600,7 +600,7 @@ void NoteFree(void *ptr) hookDebugOutput("free "); // Convert the pointer to a string and print char str[16]; - _n_ptoa32(p, str); + _n_ptoa32(ptr, str); hookDebugOutput(str); } #endif // NOTE_C_SHOW_MALLOC && !defined(NOTE_C_LOW_MEM) diff --git a/n_request.c b/n_request.c index c774720a..1c6e388a 100644 --- a/n_request.c +++ b/n_request.c @@ -436,7 +436,6 @@ J *_noteTransactionShouldLock(J *req, bool lockNotecard) // Serialize the JSON request char *json = JPrintUnformatted(req); // `json` allocated, must be freed if (json == NULL) { - _TransactionStop(); NOTE_C_LOG_ERROR(ERRSTR("failed to serialize JSON request", c_mem)); return NULL; } From d1962b768777cb6292e57dca8f8e15bd0a25430c Mon Sep 17 00:00:00 2001 From: Ray Ozzie Date: Mon, 9 Mar 2026 22:56:45 -0400 Subject: [PATCH 5/6] don't grow object exponentially; i'm trying to operate with a 2kb-4kb heap and the doubling strategy causes almost immediate failures. --- n_cjson.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/n_cjson.c b/n_cjson.c index 0e0d44f1..8061b813 100644 --- a/n_cjson.c +++ b/n_cjson.c @@ -376,16 +376,15 @@ NOTE_C_STATIC unsigned char* _ensure(printbuffer * const p, size_t needed) return NULL; } - /* calculate new buffer size */ - if (needed > (INT_MAX / 2)) { - /* overflow of int, use INT_MAX if possible */ + /* calculate new buffer size (linear growth to reduce memory waste) */ + if (needed > (INT_MAX - 256)) { if (needed <= INT_MAX) { newsize = INT_MAX; } else { return NULL; } } else { - newsize = needed * 2; + newsize = needed + 256; } /* otherwise reallocate manually */ From 3a734f0e2486acec7319e70014c0241bb766eee9 Mon Sep 17 00:00:00 2001 From: "Zachary J. Fields" Date: Tue, 10 Mar 2026 21:37:56 -0500 Subject: [PATCH 6/6] chore: Use note-c allocation scheme in cJSON --- n_cjson.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/n_cjson.c b/n_cjson.c index 8061b813..32147712 100644 --- a/n_cjson.c +++ b/n_cjson.c @@ -362,12 +362,12 @@ NOTE_C_STATIC unsigned char* _ensure(printbuffer * const p, size_t needed) return NULL; } + needed += p->offset + 1; if (needed > INT_MAX) { /* sizes bigger than INT_MAX are currently not supported */ return NULL; } - needed += p->offset + 1; if (needed <= p->length) { return p->buffer + p->offset; } @@ -376,18 +376,8 @@ NOTE_C_STATIC unsigned char* _ensure(printbuffer * const p, size_t needed) return NULL; } - /* calculate new buffer size (linear growth to reduce memory waste) */ - if (needed > (INT_MAX - 256)) { - if (needed <= INT_MAX) { - newsize = INT_MAX; - } else { - return NULL; - } - } else { - newsize = needed + 256; - } - /* otherwise reallocate manually */ + newsize = (ALLOC_CHUNK * ((needed / ALLOC_CHUNK) + ((needed % ALLOC_CHUNK) > 0))); // chunked, linear calculation for new buffer to reduce memory waste newbuffer = (unsigned char*)_Malloc(newsize); if (!newbuffer) { _Free(p->buffer);