From 2094974201fc75b07673110cc40a3e144cbd3b0d Mon Sep 17 00:00:00 2001 From: Junki Lee Date: Fri, 21 Aug 2026 13:18:26 +0900 Subject: [PATCH] Fix object key leak on insertion failure --- json_object.c | 18 ++++++++--- tests/CMakeLists.txt | 1 + tests/meson.build | 1 + tests/test_object_add_failure.c | 44 ++++++++++++++++++++++++++ tests/test_object_add_failure.expected | 1 + tests/test_object_add_failure.test | 15 +++++++++ 6 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 tests/test_object_add_failure.c create mode 100644 tests/test_object_add_failure.expected create mode 100755 tests/test_object_add_failure.test diff --git a/json_object.c b/json_object.c index 13d3826..3d431cc 100644 --- a/json_object.c +++ b/json_object.c @@ -753,11 +753,21 @@ int json_object_object_add_ex(struct json_object *jso, const char *const key, if (!existing_entry) { - const void *const k = - (opts & JSON_C_OBJECT_ADD_CONSTANT_KEY) ? (const void *)key : strdup(key); - if (k == NULL) + char *key_copy = NULL; + const void *k = key; + if (!(opts & JSON_C_OBJECT_ADD_CONSTANT_KEY)) + { + key_copy = strdup(key); + if (key_copy == NULL) + return -1; + k = key_copy; + } + if (lh_table_insert_w_hash(JC_OBJECT(jso)->c_object, k, val, hash, opts) < 0) + { + free(key_copy); return -1; - return lh_table_insert_w_hash(JC_OBJECT(jso)->c_object, k, val, hash, opts); + } + return 0; } existing_value = (json_object *)lh_entry_v(existing_entry); if (existing_value) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 51454af..0678dd1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,6 +28,7 @@ set(ALL_TEST_NAMES test_int_get test_locale test_null + test_object_add_failure test_parse test_parse_int64 test_printbuf diff --git a/tests/meson.build b/tests/meson.build index f66d28a..ca9f13d 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -19,6 +19,7 @@ test_cases = [ ['test2', 'test2.expected'], ['test4', 'test4.expected'], ['testReplaceExisting', 'testReplaceExisting.expected'], + ['test_object_add_failure', 'test_object_add_failure.expected'], ['test_cast', 'test_cast.expected'], ['test_charcase', 'test_charcase.expected'], ['test_compare', 'test_compare.expected'], diff --git a/tests/test_object_add_failure.c b/tests/test_object_add_failure.c new file mode 100644 index 0000000..774342c --- /dev/null +++ b/tests/test_object_add_failure.c @@ -0,0 +1,44 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif +#include +#include +#include + +#include "json.h" + +static void check_insert_failure(const char *key, unsigned int opts) +{ + struct json_object *object = json_object_new_object(); + struct json_object *value = json_object_new_int(7); + struct lh_table *table; + int saved_count; + int saved_size; + + assert(object != NULL); + assert(value != NULL); + table = json_object_get_object(object); + assert(table != NULL); + + /* Force lh_table_insert_w_hash() to reject the new entry. */ + saved_count = table->count; + saved_size = table->size; + table->count = INT_MAX; + table->size = INT_MAX; + + assert(json_object_object_add_ex(object, key, value, + opts | JSON_C_OBJECT_ADD_KEY_IS_NEW) == -1); + + table->count = saved_count; + table->size = saved_size; + json_object_put(value); + json_object_put(object); +} + +int main(void) +{ + check_insert_failure("copied-key", 0); + check_insert_failure("constant-key", JSON_C_OBJECT_ADD_CONSTANT_KEY); + puts("PASS"); + return 0; +} diff --git a/tests/test_object_add_failure.expected b/tests/test_object_add_failure.expected new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tests/test_object_add_failure.expected @@ -0,0 +1 @@ +PASS diff --git a/tests/test_object_add_failure.test b/tests/test_object_add_failure.test new file mode 100755 index 0000000..3912732 --- /dev/null +++ b/tests/test_object_add_failure.test @@ -0,0 +1,15 @@ +#!/bin/sh + +# Common definitions +if test -z "$srcdir"; then + srcdir="${0%/*}" + test "$srcdir" = "$0" && srcdir=. + test -z "$srcdir" && srcdir=. +fi +. "$srcdir/test-defs.sh" + +filename=$(basename "$0") +filename="${filename%.*}" + +run_output_test $filename +exit $?