From mboxrd@z Thu Jan 1 00:00:00 1970 Delivery-date: Fri, 14 Aug 2026 17:39:18 +0200 Received: from metis.whiteo.stw.pengutronix.de ([2a0a:edc0:2:b01:1d::104]) by lore.white.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wuu05-0035ek-1f for lore@lore.pengutronix.de; Fri, 14 Aug 2026 17:39:18 +0200 Received: from [127.0.0.1] (helo=metis.whiteo.stw.pengutronix.de) by metis.whiteo.stw.pengutronix.de with esmtp (Exim 4.92) (envelope-from ) id 1wutzv-0008Dh-Tx; Fri, 14 Aug 2026 17:39:07 +0200 Received: from mx1.white.stw.pengutronix.de ([185.203.200.13]) by metis.whiteo.stw.pengutronix.de with esmtp (Exim 4.92) (envelope-from ) id 1wutzN-0006kX-Rb for ptxdist@pengutronix.de; Fri, 14 Aug 2026 17:38:39 +0200 Received: from drehscheibe.grey.stw.pengutronix.de (drehscheibe.grey.stw.pengutronix.de [IPv6:2a0a:edc0:0:c01:1d::a2]) (Authenticated sender: relay-from-drehscheibe.grey.stw.pengutronix.de) by mx1.white.stw.pengutronix.de (Postfix) with ESMTPSA id A8E19201CF7; Fri, 14 Aug 2026 17:38:29 +0200 (CEST) Received: from dude05.red.stw.pengutronix.de ([2a0a:edc0:0:1101:1d::54]) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wutzJ-001TvF-1t; Fri, 14 Aug 2026 17:38:29 +0200 Received: from mol by dude05.red.stw.pengutronix.de with local (Exim 4.98.2) (envelope-from ) id 1wutzJ-00000007YpV-2E9T; Fri, 14 Aug 2026 17:38:29 +0200 From: Michael Olbrich To: ptxdist@pengutronix.de Date: Fri, 14 Aug 2026 17:38:29 +0200 Message-ID: <20260814153829.1802174-1-m.olbrich@pengutronix.de> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260716132742.3243000-1-ada@thorsis.com> References: <20260716132742.3243000-1-ada@thorsis.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on metis.whiteo.stw.pengutronix.de X-Spam-Level: X-Spam-Status: No, score=-3.5 required=5.0 tests=ALL_TRUSTED,AWL,BAYES_00 autolearn=ham autolearn_force=no version=3.4.2 Subject: Re: [ptxdist] [APPLIED] json-c: Add patches with memory fixes from upstream master X-BeenThere: ptxdist@pengutronix.de X-Mailman-Version: 2.1.29 Precedence: list List-Id: PTXdist Development Mailing List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: ptxdist@pengutronix.de Cc: Alexander Dahl Sender: "ptxdist" X-SA-Exim-Connect-IP: 127.0.0.1 X-SA-Exim-Mail-From: ptxdist-bounces@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false Thanks, applied as 7ac5a4f8c7c598618b62605131b550aa203b438b. Michael [sent from post-receive hook] On Fri, 14 Aug 2026 17:38:29 +0200, Alexander Dahl wrote: > Fixing memory leak and reference counting bugs, no new release yet. > > Signed-off-by: Alexander Dahl > Message-Id: <20260716132742.3243000-1-ada@thorsis.com> > Signed-off-by: Michael Olbrich > > diff --git a/patches/json-c-0.19/0001-Issue-934-Be-sure-to-clear-_user_delete-in-_json_obj.patch b/patches/json-c-0.19/0001-Issue-934-Be-sure-to-clear-_user_delete-in-_json_obj.patch > new file mode 100644 > index 000000000000..9d30da84ec32 > --- /dev/null > +++ b/patches/json-c-0.19/0001-Issue-934-Be-sure-to-clear-_user_delete-in-_json_obj.patch > @@ -0,0 +1,96 @@ > +From: Eric Hawicz > +Date: Sat, 4 Jul 2026 11:02:06 -0400 > +Subject: [PATCH] Issue #934: Be sure to clear _user_delete in > + _json_object_put_maybe_free() to avoid calling it twice. Add a test to check. > + > +Upstream-Status: Backport [3aa996b5b6bd96c29f2c6da98da33c3fb3d13038] > +--- > + json_object.c | 1 + > + json_object.h | 4 ++-- > + tests/test_deep_nesting.c | 33 +++++++++++++++++++++++++++++++++ > + 3 files changed, 36 insertions(+), 2 deletions(-) > + > +diff --git a/json_object.c b/json_object.c > +index 0a619679bd7e..6cb1bdac86d5 100644 > +--- a/json_object.c > ++++ b/json_object.c > +@@ -303,6 +303,7 @@ static inline int _json_object_put_maybe_free(struct json_object *jso, int free_ > + > + if (jso->_user_delete) > + jso->_user_delete(jso, jso->_userdata); > ++ jso->_user_delete = NULL; > + > + switch (jso->o_type) > + { > +diff --git a/json_object.h b/json_object.h > +index e21138ecf1db..7edb63d4f8cb 100644 > +--- a/json_object.h > ++++ b/json_object.h > +@@ -259,7 +259,7 @@ JSON_EXPORT void *json_object_get_userdata(json_object *jso); > + * The user_delete parameter is optional and may be passed as NULL, even if > + * the userdata parameter is non-NULL. It will be called just before the > + * json_object is deleted, after it's reference count goes to zero > +- * (see json_object_put()). > ++ * (see json_object_put()) but before any child objects are freed. > + * If this is not provided, it is up to the caller to free the userdata at > + * an appropriate time. (i.e. after the json_object is deleted) > + * > +@@ -293,7 +293,7 @@ JSON_EXPORT void json_object_set_userdata(json_object *jso, void *userdata, > + * The user_delete parameter is optional and may be passed as NULL, even if > + * the userdata parameter is non-NULL. It will be called just before the > + * json_object is deleted, after it's reference count goes to zero > +- * (see json_object_put()). > ++ * (see json_object_put()) but before any child objects are freed. > + * If this is not provided, it is up to the caller to free the userdata at > + * an appropriate time. (i.e. after the json_object is deleted) > + * > +diff --git a/tests/test_deep_nesting.c b/tests/test_deep_nesting.c > +index 364c4820ea5d..9e251750c480 100644 > +--- a/tests/test_deep_nesting.c > ++++ b/tests/test_deep_nesting.c > +@@ -56,6 +56,37 @@ static void test_deep_nesting_tostring(const char *str) > + json_tokener_free(tok); > + } > + > ++struct userdata_test { > ++ int userdata_val; > ++ char *p; > ++}; > ++/* > ++ * Check that the user_delete function is only called once, even with the > ++ * newer code to avoid deeply nested calls during json_object_put(). > ++ */ > ++static void user_delete_test(struct json_object *jso, void *userdata_in) > ++{ > ++ struct userdata_test *userdata = (struct userdata_test *)userdata_in; > ++ printf("in user_delete, userdata_val=%d\n", userdata->userdata_val); > ++ fflush(stdout); > ++ userdata->userdata_val = 0; > ++ userdata->p[0] = 'x'; > ++ userdata->p[8191] = 'x'; > ++ free(userdata->p); > ++} > ++static void test_nesting_with_user_delete(void) > ++{ > ++ json_object *jso; > ++ struct userdata_test userdata_val = { > ++ 1, malloc(8192) > ++ }; > ++ > ++ jso = json_object_new_object(); > ++ json_object_set_userdata(jso, &userdata_val, user_delete_test); > ++ json_object_object_add(jso, "somekey", json_object_new_string("foo")); > ++ json_object_put(jso); > ++} > ++ > + int main(int argc, char **argv) > + { > + char *str; > +@@ -77,5 +108,7 @@ int main(int argc, char **argv) > + > + free(str); > + > ++ test_nesting_with_user_delete(); > ++ > + return EXIT_SUCCESS; > + } > diff --git a/patches/json-c-0.19/0002-free-objects-outside-assert-in-json_object_put.patch b/patches/json-c-0.19/0002-free-objects-outside-assert-in-json_object_put.patch > new file mode 100644 > index 000000000000..7601bc68e47e > --- /dev/null > +++ b/patches/json-c-0.19/0002-free-objects-outside-assert-in-json_object_put.patch > @@ -0,0 +1,31 @@ > +From: Javid Khan > +Date: Tue, 30 Jun 2026 18:54:06 +0530 > +Subject: [PATCH] free objects outside assert() in json_object_put > + > +Upstream-Status: Backport [f291fa81c6f21d925ea771a8cca597f5886309cc] > +--- > + json_object.c | 7 ++++++- > + 1 file changed, 6 insertions(+), 1 deletion(-) > + > +diff --git a/json_object.c b/json_object.c > +index 6cb1bdac86d5..71cd25b2a6a9 100644 > +--- a/json_object.c > ++++ b/json_object.c > +@@ -436,11 +436,16 @@ int json_object_put(struct json_object *jso) > + // All slots are cleared, now pop back up to the parent > + { > + json_object *parent = jso->_delete_parent; > ++ int rc; > + // jso is a child that's already been detached from its parent > + // so we need to actually free it now > + assert(jso->_ref_count == 0); > + jso->_ref_count++; // We're the exclusive owner of jso, non-atomic add is ok. > +- assert(_json_object_put_maybe_free(jso, 1) == 0); > ++ // Note: the call must not be inside assert(), or it gets > ++ // compiled out when NDEBUG is defined and the memory leaks. > ++ rc = _json_object_put_maybe_free(jso, 1); > ++ assert(rc == 0); > ++ (void)rc; > + jso = parent; > + // iteration will be reset at the top of the loop > + } > diff --git a/patches/json-c-0.19/0003-Issue-934-Also-set-_userdata-NULL-in-_json_object_ma.patch b/patches/json-c-0.19/0003-Issue-934-Also-set-_userdata-NULL-in-_json_object_ma.patch > new file mode 100644 > index 000000000000..b235b00bdcf7 > --- /dev/null > +++ b/patches/json-c-0.19/0003-Issue-934-Also-set-_userdata-NULL-in-_json_object_ma.patch > @@ -0,0 +1,47 @@ > +From: Eric Hawicz > +Date: Sat, 4 Jul 2026 11:38:15 -0400 > +Subject: [PATCH] Issue #934: Also set _userdata=NULL in > + _json_object_maybe_free() so json_object_put doesn't try to use it as a > + _delete_parent value. Update the test_deep_nesting test output. > + > +Upstream-Status: Backport [0c3a5a1994ed6b5957d2e878fc1298ce9aa09b3f] > +--- > + json_object.c | 7 +++++-- > + tests/test_deep_nesting.expected | 1 + > + 2 files changed, 6 insertions(+), 2 deletions(-) > + > +diff --git a/json_object.c b/json_object.c > +index 71cd25b2a6a9..095b84e2b4ab 100644 > +--- a/json_object.c > ++++ b/json_object.c > +@@ -304,6 +304,8 @@ static inline int _json_object_put_maybe_free(struct json_object *jso, int free_ > + if (jso->_user_delete) > + jso->_user_delete(jso, jso->_userdata); > + jso->_user_delete = NULL; > ++ jso->_userdata = NULL; // aka _delete_parent, but json_object_put() will > ++ // have already grabbed it if it needs it. > + > + switch (jso->o_type) > + { > +@@ -435,10 +437,11 @@ int json_object_put(struct json_object *jso) > + > + // All slots are cleared, now pop back up to the parent > + { > +- json_object *parent = jso->_delete_parent; > +- int rc; > + // jso is a child that's already been detached from its parent > + // so we need to actually free it now > ++ // Be sure to grab _delete_parent *before* freeing jso. > ++ json_object *parent = jso->_delete_parent; > ++ int rc; > + assert(jso->_ref_count == 0); > + jso->_ref_count++; // We're the exclusive owner of jso, non-atomic add is ok. > + // Note: the call must not be inside assert(), or it gets > +diff --git a/tests/test_deep_nesting.expected b/tests/test_deep_nesting.expected > +index b869cfaabe79..b9c1ab98480f 100644 > +--- a/tests/test_deep_nesting.expected > ++++ b/tests/test_deep_nesting.expected > +@@ -1,2 +1,3 @@ > + Parsed depth 100000 string to json_object: yes > + Freed json_object > ++in user_delete, userdata_val=1 > diff --git a/patches/json-c-0.19/0004-fix-json_object_put-to-return-1-for-freed-scalars-an.patch b/patches/json-c-0.19/0004-fix-json_object_put-to-return-1-for-freed-scalars-an.patch > new file mode 100644 > index 000000000000..7e57bad294a8 > --- /dev/null > +++ b/patches/json-c-0.19/0004-fix-json_object_put-to-return-1-for-freed-scalars-an.patch > @@ -0,0 +1,195 @@ > +From: Arran Cudbard-Bell > +Date: Tue, 14 Jul 2026 09:32:03 -0400 > +Subject: [PATCH] fix json_object_put() to return 1 for freed scalars and empty > + containers > + > +The iterative rewrite in 17328a67 made json_object_put() return 0 for freed scalars, strings and empty containers, contradicting the documented contract in json_object.h. Distinguish "freed" from "still referenced" in _json_object_put_maybe_free() and add regression tests. > + > +Upstream-Status: Backport [69be99ef8e99d811d7810bdf75864ae800a67e50] > +--- > + json_object.c | 49 ++++++++++++++++++++++++++++------------ > + tests/test_deep_nesting.c | 28 ++++++++++++++++++++++- > + tests/test_deep_nesting.expected | 7 ++++++ > + 3 files changed, 69 insertions(+), 15 deletions(-) > + > +diff --git a/json_object.c b/json_object.c > +index 095b84e2b4ab..621260885620 100644 > +--- a/json_object.c > ++++ b/json_object.c > +@@ -273,13 +273,31 @@ struct json_object *json_object_get(struct json_object *jso) > + } > + > + > ++/** > ++ * Return values for _json_object_put_maybe_free(). > ++ * json_object_put_still_refd and json_object_put_freed match the documented > ++ * return values of json_object_put(), so they can be returned directly. > ++ */ > ++enum json_object_put_result > ++{ > ++ json_object_put_still_refd = 0, /* refcount decremented, object not freed */ > ++ json_object_put_freed = 1, /* refcount reached zero, memory released */ > ++ json_object_put_container = 2 /* refcount reached zero, but the object is a > ++ non-empty container: the caller must free > ++ the contents, then the object itself */ > ++}; > ++ > + /** > + * Internal json_object_put function > +- * Returns 0 if we're done "freeing" the object, either because its memory > +- * was actually released, or we just needed to decrement the refcount. > +- * Returns 1 when the object is a non-empty container that still needs to be handled. > ++ * Returns json_object_put_still_refd if a reference remains, and the object > ++ * was not freed. > ++ * Returns json_object_put_freed if the object's memory was released, either > ++ * because it holds no other objects, or because free_containers was set. > ++ * Returns json_object_put_container when the refcount reached zero but the > ++ * object is a non-empty container; the caller must free the contents, then > ++ * call this function again with free_containers set to free the object itself. > + */ > +-static inline int _json_object_put_maybe_free(struct json_object *jso, int free_containers) > ++static inline enum json_object_put_result _json_object_put_maybe_free(struct json_object *jso, int free_containers) > + { > + /* Avoid invalid free and crash explicitly instead of (silently) > + * segfaulting. > +@@ -298,7 +316,7 @@ static inline int _json_object_put_maybe_free(struct json_object *jso, int free_ > + if (--jso->_ref_count > 0) > + #endif > + { > +- return 0; // All done, caller doesn't need to do anything else > ++ return json_object_put_still_refd; > + } > + > + if (jso->_user_delete) > +@@ -315,15 +333,15 @@ static inline int _json_object_put_maybe_free(struct json_object *jso, int free_ > + json_object_object_delete(jso); > + break; > + } > +- return 1; > +- case json_type_array: > ++ return json_object_put_container; > ++ case json_type_array: > + // container objects are handled by the caller > + if (free_containers || array_list_length(JC_ARRAY(jso)->c_array) == 0) > + { > + json_object_array_delete(jso); > + break; > + } > +- return 1; > ++ return json_object_put_container; > + case json_type_string: > + json_object_string_delete(jso); > + break; > +@@ -331,16 +349,19 @@ static inline int _json_object_put_maybe_free(struct json_object *jso, int free_ > + json_object_generic_delete(jso); > + break; > + } > +- return 0; // All done, caller doesn't need to do anything else > ++ return json_object_put_freed; > + } > + > + int json_object_put(struct json_object *jso) > + { > ++ enum json_object_put_result rc; > ++ > + if (!jso) > + return 0; > + > +- if (_json_object_put_maybe_free(jso, 0) == 0) > +- return 0; > ++ rc = _json_object_put_maybe_free(jso, 0); > ++ if (rc != json_object_put_container) > ++ return (int)rc; > + // else, it's a non-empty container object, handle it below > + > + // Note: jso is now a "zombie" object, _ref_count == 0 but memory not yet released > +@@ -399,7 +420,7 @@ int json_object_put(struct json_object *jso) > + } > + > + // Now, handle actually freeing the json_object in that slot > +- if (!child || _json_object_put_maybe_free(child, 0) == 0) > ++ if (!child || _json_object_put_maybe_free(child, 0) != json_object_put_container) > + { > + // child is either freed, or still referenced somewhere else > + // leave it as-is and handle the previous slot > +@@ -441,13 +462,13 @@ int json_object_put(struct json_object *jso) > + // so we need to actually free it now > + // Be sure to grab _delete_parent *before* freeing jso. > + json_object *parent = jso->_delete_parent; > +- int rc; > ++ enum json_object_put_result rc; > + assert(jso->_ref_count == 0); > + jso->_ref_count++; // We're the exclusive owner of jso, non-atomic add is ok. > + // Note: the call must not be inside assert(), or it gets > + // compiled out when NDEBUG is defined and the memory leaks. > + rc = _json_object_put_maybe_free(jso, 1); > +- assert(rc == 0); > ++ assert(rc == json_object_put_freed); > + (void)rc; > + jso = parent; > + // iteration will be reset at the top of the loop > +diff --git a/tests/test_deep_nesting.c b/tests/test_deep_nesting.c > +index 9e251750c480..4bf24578daa1 100644 > +--- a/tests/test_deep_nesting.c > ++++ b/tests/test_deep_nesting.c > +@@ -81,12 +81,36 @@ static void test_nesting_with_user_delete(void) > + 1, malloc(8192) > + }; > + > +- jso = json_object_new_object(); > ++ jso = json_object_new_object(); > + json_object_set_userdata(jso, &userdata_val, user_delete_test); > + json_object_object_add(jso, "somekey", json_object_new_string("foo")); > + json_object_put(jso); > + } > + > ++/* > ++ * Check that json_object_put() returns 1 whenever the object is freed, > ++ * including for scalars and empty containers, and 0 only when a > ++ * reference remains. > ++ */ > ++static void test_put_return_values(void) > ++{ > ++ json_object *jso; > ++ > ++ printf("put(empty object) returned %d\n", json_object_put(json_object_new_object())); > ++ printf("put(empty array) returned %d\n", json_object_put(json_object_new_array())); > ++ printf("put(string) returned %d\n", json_object_put(json_object_new_string("foo"))); > ++ printf("put(int) returned %d\n", json_object_put(json_object_new_int(42))); > ++ > ++ jso = json_object_new_object(); > ++ json_object_object_add(jso, "somekey", json_object_new_string("foo")); > ++ printf("put(non-empty object) returned %d\n", json_object_put(jso)); > ++ > ++ jso = json_object_new_object(); > ++ json_object_get(jso); > ++ printf("put(still-referenced object) returned %d\n", json_object_put(jso)); > ++ printf("put(last reference) returned %d\n", json_object_put(jso)); > ++} > ++ > + int main(int argc, char **argv) > + { > + char *str; > +@@ -110,5 +134,7 @@ int main(int argc, char **argv) > + > + test_nesting_with_user_delete(); > + > ++ test_put_return_values(); > ++ > + return EXIT_SUCCESS; > + } > +diff --git a/tests/test_deep_nesting.expected b/tests/test_deep_nesting.expected > +index b9c1ab98480f..2449ede3265c 100644 > +--- a/tests/test_deep_nesting.expected > ++++ b/tests/test_deep_nesting.expected > +@@ -1,3 +1,10 @@ > + Parsed depth 100000 string to json_object: yes > + Freed json_object > + in user_delete, userdata_val=1 > ++put(empty object) returned 1 > ++put(empty array) returned 1 > ++put(string) returned 1 > ++put(int) returned 1 > ++put(non-empty object) returned 1 > ++put(still-referenced object) returned 0 > ++put(last reference) returned 1 > diff --git a/patches/json-c-0.19/series b/patches/json-c-0.19/series > new file mode 100644 > index 000000000000..311299a3ac45 > --- /dev/null > +++ b/patches/json-c-0.19/series > @@ -0,0 +1,7 @@ > +# generated by git-ptx-patches > +#tag:base --start-number 1 > +0001-Issue-934-Be-sure-to-clear-_user_delete-in-_json_obj.patch > +0002-free-objects-outside-assert-in-json_object_put.patch > +0003-Issue-934-Also-set-_userdata-NULL-in-_json_object_ma.patch > +0004-fix-json_object_put-to-return-1-for-freed-scalars-an.patch > +# 5aff3fd086034e7fb3fdec97d3808ade - git-ptx-patches magic