Summary
Parsing a JSON string that ends in a lone backslash causes json_get_escape_len() to read one byte past the end of the caller-supplied buffer, under the counted-length API json_walk(str, len, ...) / json_scanf. The buffer is not required to be NUL-terminated by the API, so the read is genuinely out of bounds. CWE-125, AddressSanitizer-confirmed.
Affected: current master (commit a42fc33), likely earlier revisions with the same call site.
Root cause
At frozen.c:247 (in json_parse_string):
EXPECT((n = json_get_escape_len(f->cur + 1, json_left(f))) > 0, n);
The pointer is advanced by one (f->cur + 1, to skip the backslash), but the length passed is json_left(f) = f->end - f->cur, the count measured before the advance. It is one greater than the bytes available at f->cur + 1. When the input ends at the backslash, zero bytes remain, but json_get_escape_len is told len == 1 and dereferences its argument before any length check:
static int json_get_escape_len(const char *s, int len) {
switch (*s) { // reads *s (== f->end) one byte past the buffer
ASan reports a heap-buffer-overflow READ of size 1 at frozen.c:188 via json_parse_string (frozen.c:248). Note: a len <= 0 guard alone does not fix it, because the caller passes len == 1, not 0.
Reproducer (self-contained, no fuzzer)
/* cc -fsanitize=address -O0 poc.c frozen.c -o poc ; ./poc /
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "frozen.h"
static void cb(voida,const charb,size_t c,const chard,const struct json_tokene){
(void)a;(void)b;(void)c;(void)d;(void)e; }
int main(void){
const char in[] = {'"','\'}; / NOT NUL-terminated; exactly 2 bytes /
char buf = (char)malloc(sizeof in); / exact length so the over-read faults */
memcpy(buf, in, sizeof in);
json_walk(buf, (int)sizeof in, cb, 0);
free(buf);
return 0;
}
Output: AddressSanitizer: heap-buffer-overflow READ of size 1 ... in json_get_escape_len. Trigger: any JSON string ending in a single unescaped backslash; minimal input is the 2 bytes 0x22 0x5C (").
Impact
Out-of-bounds read only: a crash (DoS) in hardened/ASan builds or when the byte falls on an unmapped page, and at most a 1-byte adjacent-heap disclosure. No out-of-bounds write, no code execution. Reachable wherever frozen parses attacker-influenced JSON.
Suggested fix (two hunks)
Pass a length matching the advanced pointer at frozen.c:247:
EXPECT((n = json_get_escape_len(f->cur + 1, json_left(f) - 1)) > 0, n);
Bound the read in json_get_escape_len before switch (*s):
if (len <= 0) return JSON_STRING_INCOMPLETE;
Summary
Parsing a JSON string that ends in a lone backslash causes json_get_escape_len() to read one byte past the end of the caller-supplied buffer, under the counted-length API json_walk(str, len, ...) / json_scanf. The buffer is not required to be NUL-terminated by the API, so the read is genuinely out of bounds. CWE-125, AddressSanitizer-confirmed.
Affected: current master (commit a42fc33), likely earlier revisions with the same call site.
Root cause
At frozen.c:247 (in json_parse_string):
EXPECT((n = json_get_escape_len(f->cur + 1, json_left(f))) > 0, n);
The pointer is advanced by one (f->cur + 1, to skip the backslash), but the length passed is json_left(f) = f->end - f->cur, the count measured before the advance. It is one greater than the bytes available at f->cur + 1. When the input ends at the backslash, zero bytes remain, but json_get_escape_len is told len == 1 and dereferences its argument before any length check:
static int json_get_escape_len(const char *s, int len) {
switch (*s) { // reads *s (== f->end) one byte past the buffer
ASan reports a heap-buffer-overflow READ of size 1 at frozen.c:188 via json_parse_string (frozen.c:248). Note: a len <= 0 guard alone does not fix it, because the caller passes len == 1, not 0.
Reproducer (self-contained, no fuzzer)
/* cc -fsanitize=address -O0 poc.c frozen.c -o poc ; ./poc /
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "frozen.h"
static void cb(voida,const charb,size_t c,const chard,const struct json_tokene){
(void)a;(void)b;(void)c;(void)d;(void)e; }
int main(void){
const char in[] = {'"','\'}; / NOT NUL-terminated; exactly 2 bytes /
char buf = (char)malloc(sizeof in); / exact length so the over-read faults */
memcpy(buf, in, sizeof in);
json_walk(buf, (int)sizeof in, cb, 0);
free(buf);
return 0;
}
Output: AddressSanitizer: heap-buffer-overflow READ of size 1 ... in json_get_escape_len. Trigger: any JSON string ending in a single unescaped backslash; minimal input is the 2 bytes 0x22 0x5C (").
Impact
Out-of-bounds read only: a crash (DoS) in hardened/ASan builds or when the byte falls on an unmapped page, and at most a 1-byte adjacent-heap disclosure. No out-of-bounds write, no code execution. Reachable wherever frozen parses attacker-influenced JSON.
Suggested fix (two hunks)
Pass a length matching the advanced pointer at frozen.c:247:
EXPECT((n = json_get_escape_len(f->cur + 1, json_left(f) - 1)) > 0, n);
Bound the read in json_get_escape_len before switch (*s):
if (len <= 0) return JSON_STRING_INCOMPLETE;