[BUG] cJSON_PrintBuffered(item, 0, ...) with custom hooks: ensure() reads 1 byte from a zero-sized buffer (memcpy(..., p->offset + 1))
Los mantenedores suelen responder en 12 días
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 70/100
Línea de trabajo
Empieza con ensure() en cJSON.c, en la rama de reasignación manual descrita en el issue, y luego sigue cómo cJSON_PrintBuffered() inicializa el búfer cuando prebuffer es cero. Reproduce el problema con el asignador adjunto y compara los valores 0 y 1 de prebuffer. El trabajo estará terminado cuando se manejen los búferes de tamaño cero sin lecturas fuera de los límites y la reproducción imprima la cadena correctamente.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Summary
cJSON_PrintBuffered() accepts prebuffer == 0 (only negative values are rejected). With hooks installed via cJSON_InitHooks() that do not provide reallocate (i.e. the manual-reallocation branch of ensure()), the first growth does
memcpy(newbuffer, p->buffer, p->offset + 1); /* cJSON.c:523 in v1.7.18 */
with p->length == 0 and p->offset == 0, i.e. it reads 1 byte from an object whose size is 0. This is an out-of-bounds read (undefined behaviour).
Affected code
cJSON_PrintBuffered():prebuffer < 0is rejected,0is passed toglobal_hooks.allocate(0);p.length = 0.ensure(): the(p->length > 0) && ...guard is skipped for length 0, then the manual branch copiesoffset + 1bytes from the old buffer, which haslength == 0.
Not affected: default hooks (malloc/free -> realloc branch), and allocators returning NULL for allocate(0) (function returns early).
Why this was not noticed earlier
Per the C standard (ISO C99 §7.20.3 / C11 §7.22.3), a zero-sized allocation request has implementation-defined behaviour: the allocator may either return a null pointer, or return a non-null pointer with the explicit requirement that "the returned pointer shall not be used to access an object." Even when allocate(0) returns a non-NULL pointer, accessing any byte through it is illegal.
In practice, common runtime environments masked this out-of-bounds read in different ways:
- Linux / glibc (
ptmalloc) has a minimum allocation unit: When custom hooks wrappingmalloc/freeare installed on Linux (entering the manualmemcpybranch),malloc(0)rounds the request up to the minimum heap chunk size (MINSIZE, typically 32 bytes on 64-bit targets with 16–24 bytes of usable payload). As a result,memcpy(newbuffer, p->buffer, 1)reads 1 uninitialised byte from inside that minimum chunk without faulting, and that byte is immediately overwritten by the subsequent print routine. - FreeRTOS (
pvPortMalloc) returnsNULL(0) for 0-byte requests: Standard FreeRTOS heap implementations (heap_1,heap_2,heap_4,heap_5) guard allocation withif (xWantedSize > 0)and returnNULLwhen given0. Consequently,cJSON_PrintBuffered()hitsif (!p.buffer) return NULL;and exits before ever reachingensure(). - Default hooks use
realloc: WithoutcJSON_InitHooks(),ensure()usesrealloc()rather than the manualallocate + memcpy + deallocatepath.
However, under any allocator that returns a non-dereferenceable non-null pointer for size 0 (such as sentinel-pointer allocators like ZERO_SIZE_PTR, slab/pool allocators returning an end-of-page boundary, or guard-page/hardened allocators), reading 1 byte from the 0-sized buffer immediately faults.
Reproduction
Self-contained repro.c (attached below). The custom allocator returns, for a 0-byte request, a pointer to the start of an inaccessible page, so the out-of-bounds read becomes observable. This only makes the UB visible; it is a legal return value for malloc(0)-style allocators.
gcc -g -I<cJSON> repro.c <cJSON>/cJSON.c -lm -o repro
./repro 1 # prebuffer=1 -> "abc"
./repro 0 # Segmentation fault
gdb backtrace (v1.7.18):
#1 ensure (p=..., needed=7) at cJSON.c:523
#2 print_string_ptr (input="abc", ...) at cJSON.c:964
#3 print_string ...
#4 print_value ...
#5 cJSON_PrintBuffered (item=..., prebuffer=0, fmt=0) at cJSON.c:1295
Control: prebuffer = 1 prints "abc" correctly. Same 0-crash / 1-ok result observed on v1.7.11, v1.7.18 and master at v1.7.19-6-g6d9f244.
Note: AddressSanitizer does not report this with a system malloc(0)-backed allocator, because glibc/ASan hand out a >= 1 byte block for size 0, so reading byte 0 stays in bounds. The bug is the missing guarantee, not the specific crash.
Discovery
Found during AI-assisted analysis of the compiled cJSON binary (model-proposed hypothesis from disassembly: "ensure() may copy one byte too many when length is 0"); the public-API trigger and the allocator above were constructed and verified manually.
Attachment: repro.c
/* gcc -g -I<cJSON dir> repro.c <cJSON dir>/cJSON.c -lm -o repro ; ./repro 0 ; ./repro 1 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include "cJSON.h"
/* Allocator that returns a pointer to an inaccessible page for a 0-byte request,
making the out-of-bounds read of a zero-sized object observable. */
static void *zero_block;
static void *my_malloc(size_t n)
{
if (n == 0) {
long pg = sysconf(_SC_PAGESIZE);
char *m = mmap(NULL, 2 * pg, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
mprotect(m + pg, pg, PROT_NONE);
return zero_block = m + pg;
}
return malloc(n);
}
static void my_free(void *p) { if (p != zero_block) free(p); }
int main(int argc, char **argv)
{
int prebuffer = argc > 1 ? atoi(argv[1]) : 0;
cJSON_Hooks h = { my_malloc, my_free };
cJSON_InitHooks(&h);
cJSON *s = cJSON_CreateString("abc");
char *out = cJSON_PrintBuffered(s, prebuffer, 0);
printf("prebuffer=%d -> %s\n", prebuffer, out ? out : "NULL");
my_free(out);
cJSON_Delete(s);
return 0;
}
- Lenguaje dominante
- C
- Estrellas
- 13.1k
- Forks
- 3.6k
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de DaveGamble/cJSON
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
DaveGamble/cJSON#1106 ·
Los mantenedores suelen responder en 12 días
-
Undefined-Behavior in function cJSON_SetNumberHelperPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
DaveGamble/cJSON#1100 · 1 reacción ·
Los mantenedores suelen responder en 12 días
-
CMake config files contain absolute install pathsPosiblemente ocupada @akashdeshmukh-git la tomó hace 10 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
DaveGamble/cJSON#1094 · 9 comentarios · 1 reacción ·
Los mantenedores suelen responder en 12 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
DaveGamble/cJSON#1093 ·
Los mantenedores suelen responder en 12 días
-
Use-after-free in cJSONUtils_ApplyPatches when a patch removes the patch arrayPosiblemente ocupada @iliasabk la tomó hace 44 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
DaveGamble/cJSON#1082 ·
Los mantenedores suelen responder en 12 días
Todos los issues de DaveGamble/cJSON
Issues similares
-
documentation severity: low
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
luainkernel/lunatik#2231 ·
Los mantenedores suelen responder en 1 día
-
aspect ratio 16:10Abiertoenhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
Revivedx/SavageWindRecomp#402 ·
Los mantenedores suelen responder en 1 día
-
cleanup help wanted
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
ESPresense/ESPresense#2602 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
apache/arrow-swift#209 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
libretiny-eu/libretiny#435 ·
Los mantenedores suelen responder en 4 días