Skip to content

Heap out-of-bounds read in json_get_escape_len on a trailing backslash #85

Description

@eobi

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(void
a,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;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions