Closed Samsung-PSIRT closed 1 week ago
json2cbor is just a tool and therefore not subject to the same security guarantees as the library itself. Therefore, I consider this a regular bug.
I can't reproduce with a simple file and valgrind:
$ cat /tmp/src.json
[6.4]
$ valgrind ./bin/json2cbor /tmp/src.json | xxd
==1142061== Memcheck, a memory error detector
==1142061== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==1142061== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==1142061== Command: ./bin/json2cbor /tmp/src.json
==1142061==
==1142061==
==1142061== HEAP SUMMARY:
==1142061== in use at exit: 0 bytes in 0 blocks
==1142061== total heap usage: 8 allocs, 8 frees, 83,558 bytes allocated
==1142061==
==1142061== All heap blocks were freed -- no leaks are possible
==1142061==
==1142061== For lists of detected and suppressed errors, rerun with: -s
==1142061== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
00000000: 81fb 4019 9999 9999 999a ..@.......
I don't understand your logic. You're saying:
Here decode_json() recursively calls itself to decode the contents of array, which is double value 6.4. While doing this, 9 bytes are required, which is less than 6 bytes initially allocated for "[6.4]" string, thus, memory extension happens:
Indeed and that's clearly marked in the code you pasted: https://github.com/intel/tinycbor/blob/26c63e3d5977f77a6483edde4519489254670375/tools/json2cbor/json2cbor.c#L325-L341
Thus in the line
return cbor_encoder_close_container_checked(encoder, &container);
variablecontainer
already hosts a new memory pointer, whileencoder
still keeps the old one.
Well, no, that's impossible. As you can see in the code above, we restore encoder
from the old state that was saved in container
, then loop back in the goto
statement. When that happens, container
is saved from encoder
, so at that point both variables are identical again.
What's more, we do not call cbor_encoder_close_container_checked
at all in this recursion level. We do return CborNoError
and the previous level does call that function.
Modifying the section of code to ensure the buffers always change:
if (err == CborErrorOutOfMemory) {
buffersize += 1024;
uint8_t *newbuffer = malloc(buffersize);
if (newbuffer == NULL)
return err;
memcpy(newbuffer, buffer, buffersize - 1024);
free(buffer);
Before that free
, in my debugger, I see:
(gdb) p buffer
$1 = (uint8_t *) 0x4204a0 "\2016.4]\n"
(gdb) p newbuffer
$2 = (uint8_t *) 0x41f530 "\2016.4]\n"
By the time we reach call to cbor_encode_double
again, encoder->data.ptr
and container.data.ptr
are 0x41f531, so they're pointing to the new buffer. After the return, when we get back to the outer level, the caller's container.data.ptr
is 0x41f53a, so pointing to the new buffer.
It calls cbor_encoder_close_container_checked
, which does not read the first parameter, only writes to it:
https://github.com/intel/tinycbor/blob/26c63e3d5977f77a6483edde4519489254670375/src/cborencoder.c#L574-L590
Uh, the code you pasted for cbor_encoder_close_container_checked
is very different than what's in the library today. Looks like you're missing cc2bfbb20954f0be9237624b53feca0e27e88f72, which was included in release 0.5.0.
Please upgrade, your version is too old.
Issue: a pointer to input buffer is stored in two places, then in one of them memory is re-alloced, but old pointer survives in the second place, then used. This causes access to the memory that is not already valid. Source code: tinycbor/tools/json2cbor/json2cbor.c
Sanitizer says:
The file
crash-9a7c570f506fb55b4166b7dcfada2e08cee83d79
contains the string "[6.4]" which is JSON array of 1 element of typedouble
and value 6.4.To reproduce:
Further logic is a bit complicated:
Here
decode_json()
recursively calls itself to decode the contents of array, which isdouble
value 6.4. While doing this, 9 bytes are required, which is less than 6 bytes initially allocated for "[6.4]" string, thus, memory extension happens:Thus in the line
return cbor_encoder_close_container_checked(encoder, &container);
variablecontainer
already hosts a new memory pointer, whileencoder
still keeps the old one. The next call:and finally crash here:
Fix recommendations: synchronize pointers in all places where
decode_json()
is called, for example here:Note:
json2cbor()
was used because it simplifies injection of rich JSON fuzzing corpus into both cJSON and CBOR parsers. Thus, the tooljson2cbor
on its own may not be that important, but a similar way of calling TinyCBOR in other TizenLite sources may be practiced with the similarrealloc()
issues