-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Open
Description
Description
decode_pointer_inplace() (used by JSON Patch helpers) writes decoded slashes to decoded_string[1] instead of the current output slot. As a result, pointers containing ~1 are decoded incorrectly during patch application—values are written under mutated object keys like "a~/" instead of the intended "a/b". The function reports success even though it corrupted the object.
Reproduction steps
- Clone
cJSON(currentmaster). - Drop the PoC below into
poc_decode_pointer.cat repo root. - Build and run:
cd cJSON gcc -std=c99 -Wall -Wextra poc_decode_pointer.c cJSON.c cJSON_Utils.c -o poc_decode_pointer ./poc_decode_pointer
PoC source
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
#include "cJSON_Utils.h"
static void print_json(const char *label, cJSON *item)
{
char *rendered = cJSON_PrintUnformatted(item);
if (rendered == NULL)
{
fprintf(stderr, "%s: <print error>\n", label);
return;
}
printf("%s: %s\n", label, rendered);
cJSON_free(rendered);
}
int main(void)
{
const char *document_text = "{\"a/b\":1}";
const char *patch_text = "[{\"op\":\"add\",\"path\":\"/a~1b\",\"value\":2}]";
cJSON *document = NULL;
cJSON *patches = NULL;
cJSON *value = NULL;
int status = 0;
document = cJSON_Parse(document_text);
patches = cJSON_Parse(patch_text);
if ((document == NULL) || (patches == NULL))
{
fprintf(stderr, "failed to parse JSON input\n");
goto cleanup;
}
print_json("Original document", document);
print_json("Patch", patches);
status = cJSONUtils_ApplyPatches(document, patches);
printf("cJSONUtils_ApplyPatches returned %d\n", status);
print_json("Document after patch", document);
value = cJSON_GetObjectItemCaseSensitive(document, "a/b");
if (value != NULL)
{
printf("a/b = %d\n", value->valueint);
}
else
{
printf("a/b key is missing!\n");
}
value = cJSON_GetObjectItemCaseSensitive(document, "a~/");
if (value != NULL)
{
printf("Corrupted key a~/ = %d\n", value->valueint);
}
cleanup:
if (document != NULL)
{
cJSON_Delete(document);
}
if (patches != NULL)
{
cJSON_Delete(patches);
}
return status;
}PoC Output
zc@docker:~/cJSON$ ./poc_decode_pointer
Original document: {"a/b":1}
Patch: [{"op":"add","path":"/a~1b","value":2}]
cJSONUtils_ApplyPatches returned 0
Document after patch: {"a/b":1,"a~/":2}
a/b = 1
Corrupted key a~/ = 2
Expected vs. actual results
- Expected: Applying the patch that targets
/a~1bupdates key"a/b"to 2. - Actual:
cJSONUtils_ApplyPatchesreturns success but leaves"a/b"unchanged and inserts a new key"a~/"with value 2, proving the decoded pointer got corrupted.
Suggested fix
In decode_pointer_inplace() (around line 370), write the decoded slash to decoded_string[0] and ensure the output pointer only advances once per decoded character:
diff --git a/cJSON_Utils.c b/cJSON_Utils.c
index 8fa24f8..8a3d881 100644
--- a/cJSON_Utils.c
+++ b/cJSON_Utils.c
@@ -374,7 +374,7 @@ static void decode_pointer_inplace(unsigned char
*string)
}
else if (string[1] == '1')
{
- decoded_string[1] = '/';
+ decoded_string[0] = '/';
}
else
{
@@ -383,7 +383,10 @@ static void decode_pointer_inplace(unsigned char
*string)
}
string++;
+ continue;
}
+
+ decoded_string[0] = string[0];
}
decoded_string[0] = '\0';This change keeps the decoded pointer aligned with the output buffer so /a~1b resolves to "a/b" during JSON Patch operations.
Output after fix
zc@docker:~/cJSON$ ./poc_decode_pointer
Original document: {"a/b":1}
Patch: [{"op":"add","path":"/a~1b","value":2}]
cJSONUtils_ApplyPatches returned 0
Document after patch: {"a/b":2}
a/b = 2
Metadata
Metadata
Assignees
Labels
No labels