Skip to content

Stack overflow in JSON schema validation with cyclic $ref #389

@lbw15507

Description

@lbw15507

Description

I found a reproducible stack overflow in the JSON schema validator on the current master branch.

A schema containing a cyclic $ref, such as {"$ref":"#"}, causes ucl_schema_validate() to recursively validate the same schema without a recursion limit or reference-cycle detection. This eventually exhausts the stack and crashes.

Version

Tested on current master:

ed8617c565083c81939cfb99f1594af1cb539850

Environment

  • OS: Linux x86_64
  • Compiler: clang with AddressSanitizer and UndefinedBehaviorSanitizer
  • Sanitizers: AddressSanitizer + UndefinedBehaviorSanitizer

Reproduction

Minimal schema:

{"$ref":"#"}

Any valid document is enough to trigger the recursion, for example:

{"a":1}

The attached PoC zip contains:

  • N1_schema_only.txt: the 12-byte schema {"$ref":"#"}.
  • N1_schema_selfref_fuzzer_input.bin: the fuzzer input with a 1-byte selector followed by the schema.
  • verify_schema.c: a standalone reproducer using ucl_object_validate().

Example build and run:

cd libucl
clang -fsanitize=address,undefined -fno-omit-frame-pointer -g -O1 \
  -I ./include -I ./src -I ./uthash -I ./klib \
  verify_schema.c build-fuzz/libucl.a -lm -lrt -o verify_schema

ASAN_OPTIONS=detect_leaks=0 ./verify_schema 1

The same root cause can also be triggered by an indirect $ref cycle:

{"$ref":"#/definitions/a","definitions":{"a":{"$ref":"#/definitions/a"}}}

The standalone reproducer includes this as:

ASAN_OPTIONS=detect_leaks=0 ./verify_schema 2

ASAN output

ERROR: AddressSanitizer: stack-overflow

#0 __interceptor_strlen
#1 ucl_object_lookup src/ucl_util.c:2677
#2 ucl_schema_validate src/ucl_schema.c:962
#3 ucl_schema_validate src/ucl_schema.c:1040
#4 ucl_schema_validate src/ucl_schema.c:1040
#5 ucl_schema_validate src/ucl_schema.c:1040
...

The repeated frame is:

ucl_schema_validate src/ucl_schema.c:1040

Root cause hypothesis

ucl_schema_validate() resolves $ref and then recursively calls itself:

elt = ucl_object_lookup(schema, "$ref");
if (elt != NULL) {
    ref_root = root;
    cur = ucl_schema_resolve_ref(root, ucl_object_tostring(elt),
                                 err, external_refs, &ref_root);
    if (cur == NULL) {
        return false;
    }
    if (!ucl_schema_validate(cur, obj, try_array, err, ref_root,
                             external_refs)) {
        return false;
    }
}

For {"$ref":"#"}, ucl_schema_resolve_ref() returns the root schema itself. The validator then recursively validates the same schema and object again, without consuming document depth and without detecting the $ref cycle.

Notes

This is a possible denial-of-service issue when schemas can be provided by an untrusted or semi-trusted source. A recursion depth limit or $ref cycle detection would prevent this class of crash.

N1_schema_ref_cycle_poc.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions