diff mbox series

[meta-oe,wrynose,3/42] cjson: patch CVE-2026-87933

Message ID 20260923104056.457360-3-ankur.tyagi85@gmail.com
State New
Headers show
Series [meta-oe,wrynose,1/42] c-ares: mark CVEs fixed | expand

Commit Message

Ankur Tyagi Sept. 23, 2026, 10:40 a.m. UTC
From: Ankur Tyagi <ankur.tyagi85@gmail.com>

Details:
https://nvd.nist.gov/vuln/detail/cve-2026-87933

Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
---
 .../cjson/cjson/CVE-2026-87933.patch          | 110 ++++++++++++++++++
 .../recipes-devtools/cjson/cjson_1.7.19.bb    |   1 +
 2 files changed, 111 insertions(+)
 create mode 100644 meta-oe/recipes-devtools/cjson/cjson/CVE-2026-87933.patch
diff mbox series

Patch

diff --git a/meta-oe/recipes-devtools/cjson/cjson/CVE-2026-87933.patch b/meta-oe/recipes-devtools/cjson/cjson/CVE-2026-87933.patch
new file mode 100644
index 0000000000..3972e5eafc
--- /dev/null
+++ b/meta-oe/recipes-devtools/cjson/cjson/CVE-2026-87933.patch
@@ -0,0 +1,110 @@ 
+From a1b370963c98e1374e5538e97841f9d476a8932e Mon Sep 17 00:00:00 2001
+From: lilu5458 <lilu@kylinos.cn>
+Date: Wed, 16 Sep 2026 09:55:35 +0800
+Subject: [PATCH] Fix: heap-use-after-free in merge_patch when patch is subtree
+ of target (#1065)
+
+When cJSONUtils_MergePatch(target, patch) is called with a non-object
+patch (scalar, array, or NULL) that happens to be a subtree of target,
+merge_patch() called cJSON_Delete(target) first, which freed the patch
+memory, and then cJSON_Duplicate(patch, 1) read the already-freed memory,
+triggering a heap-use-after-free (detected by AddressSanitizer at
+cJSON_Duplicate_rec, cJSON.c:2808).
+
+Fix: duplicate the patch first into a local variable, then delete the
+target, then return the duplicate. This matches the Option B approach
+proposed in issue #1060.
+
+Verified locally:
+- Reproduced the UAF with a minimal PoC under ASan before the fix.
+- After the fix the PoC runs cleanly (exit 0, correct result [1,2,3]).
+- Added a regression unit test
+  (merge_patch_should_not_read_freed_memory_when_patch_is_subtree).
+- Full ctest suite passes (22/22 tests).
+
+Fixes #1060
+
+Signed-off-by: lilu <lilu@kylinos.cn>
+(cherry picked from commit 6d9f2443ab071f86e5d9b43025a40929ec41c46c)
+
+CVE: CVE-2026-87933
+Upstream-Status: Backport [https://github.com/DaveGamble/cJSON/commit/6d9f2443ab071f86e5d9b43025a40929ec41c46c]
+
+Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
+---
+ cJSON_Utils.c           |  8 ++++++--
+ tests/old_utils_tests.c | 34 ++++++++++++++++++++++++++++++++++
+ 2 files changed, 40 insertions(+), 2 deletions(-)
+
+diff --git a/cJSON_Utils.c b/cJSON_Utils.c
+index 8fa24f8..6f41875 100644
+--- a/cJSON_Utils.c
++++ b/cJSON_Utils.c
+@@ -1324,9 +1324,13 @@ static cJSON *merge_patch(cJSON *target, const cJSON * const patch, const cJSON_
+ 
+     if (!cJSON_IsObject(patch))
+     {
+-        /* scalar value, array or NULL, just duplicate */
++        /* scalar value, array or NULL, just duplicate.
++         * Duplicate the patch first in case it is a subtree of target,
++         * otherwise cJSON_Delete(target) would free the patch memory
++         * and the subsequent cJSON_Duplicate would read freed memory. */
++        cJSON *duplicate = cJSON_Duplicate(patch, 1);
+         cJSON_Delete(target);
+-        return cJSON_Duplicate(patch, 1);
++        return duplicate;
+     }
+ 
+     if (!cJSON_IsObject(target))
+diff --git a/tests/old_utils_tests.c b/tests/old_utils_tests.c
+index 690dbb5..bdc7393 100644
+--- a/tests/old_utils_tests.c
++++ b/tests/old_utils_tests.c
+@@ -189,6 +189,39 @@ static void merge_tests(void)
+     }
+ }
+ 
++static void merge_patch_should_not_read_freed_memory_when_patch_is_subtree(void)
++{
++    /* When patch is a subtree of target, merge_patch must duplicate the patch
++     * before deleting target. Otherwise cJSON_Delete(target) frees the patch
++     * memory and the subsequent cJSON_Duplicate reads freed memory (UAF).
++     * See CVE candidate: heap-use-after-free in merge_patch (cJSON_Utils.c). */
++    cJSON *target = cJSON_Parse("{\"a\":[1,2,3]}");
++    cJSON *patch = cJSON_GetObjectItem(target, "a");
++    cJSON *result = NULL;
++    cJSON *first = NULL;
++    cJSON *second = NULL;
++    cJSON *third = NULL;
++
++    TEST_ASSERT_NOT_NULL(target);
++    TEST_ASSERT_NOT_NULL(patch);
++
++    /* patch (array [1,2,3]) is a subtree of target. This used to trigger
++     * heap-use-after-free under AddressSanitizer before the fix. */
++    result = cJSONUtils_MergePatch(target, patch);
++    TEST_ASSERT_NOT_NULL(result);
++    TEST_ASSERT_TRUE(cJSON_IsArray(result));
++    TEST_ASSERT_EQUAL_INT(3, cJSON_GetArraySize(result));
++
++    first = cJSON_GetArrayItem(result, 0);
++    second = cJSON_GetArrayItem(result, 1);
++    third = cJSON_GetArrayItem(result, 2);
++    TEST_ASSERT_EQUAL_INT(1, first->valueint);
++    TEST_ASSERT_EQUAL_INT(2, second->valueint);
++    TEST_ASSERT_EQUAL_INT(3, third->valueint);
++
++    cJSON_Delete(result);
++}
++
+ static void generate_merge_tests(void)
+ {
+     size_t i = 0;
+@@ -219,6 +252,7 @@ int main(void)
+     RUN_TEST(misc_tests);
+     RUN_TEST(sort_tests);
+     RUN_TEST(merge_tests);
++    RUN_TEST(merge_patch_should_not_read_freed_memory_when_patch_is_subtree);
+     RUN_TEST(generate_merge_tests);
+ 
+     return UNITY_END();
diff --git a/meta-oe/recipes-devtools/cjson/cjson_1.7.19.bb b/meta-oe/recipes-devtools/cjson/cjson_1.7.19.bb
index d914018331..b4e57d7297 100644
--- a/meta-oe/recipes-devtools/cjson/cjson_1.7.19.bb
+++ b/meta-oe/recipes-devtools/cjson/cjson_1.7.19.bb
@@ -7,6 +7,7 @@  LIC_FILES_CHKSUM = "file://LICENSE;md5=218947f77e8cb8e2fa02918dc41c50d0"
 SRC_URI = "git://github.com/DaveGamble/cJSON.git;branch=master;protocol=https \
            file://run-ptest \
            file://0001-allow-build-with-cmake-4.patch \
+           file://CVE-2026-87933.patch \
          "
 SRCREV = "c859b25da02955fef659d658b8f324b5cde87be3"