Skip to content

[BUG] NULL pointer dereference in ucl_schema_validate when err parameter is NULL #369

@MarkLee131

Description

@MarkLee131

ucl_object_validate() crashes if you pass NULL for the err parameter, which the API documents as optional. The internal function ucl_schema_validate() writes to err->code directly in two places (src/ucl_schema.c:994 and :1023) without a NULL guard. The rest of the file uses ucl_schema_create_error() which does check for NULL, so these two spots were just missed. Affects 0.9.4 and earlier. (CWE-476). The two unguarded writes:

Location 1 (ucl_schema.c:994), after anyOf succeeds:

elt = ucl_object_lookup(schema, "anyOf");
if (elt != NULL && elt->type == UCL_ARRAY) {
    // ... validation loop ...
    if (!ret) {
        return false;
    }
    else {
        /* Reset error */
        err->code = UCL_SCHEMA_OK;  // CRASH if err == NULL
    }
}

Location 2 (ucl_schema.c:1023), after not succeeds:

elt = ucl_object_lookup(schema, "not");
if (elt != NULL && elt->type == UCL_OBJECT) {
    if (ucl_schema_validate(elt, obj, true, err, root, external_refs)) {
        return false;
    }
    else {
        err->code = UCL_SCHEMA_OK;  // CRASH if err == NULL
    }
}

Reproduction

Found by fuzzing (-fsanitize=fuzzer,address). Minimal reproducer:

#include <ucl.h>
#include <string.h>

int main(void) {
    /* Parse a schema with anyOf: first alternative won't match,
     * second will, so the anyOf "success" path is taken */
    struct ucl_parser *sp = ucl_parser_new(0);
    const char *schema_str = "{\"anyOf\": [{\"type\": \"integer\"}, {\"type\": \"string\"}]}";
    ucl_parser_add_string(sp, schema_str, strlen(schema_str));
    ucl_object_t *schema = ucl_parser_get_object(sp);
    ucl_parser_free(sp);

    /* Create a string object to validate */
    ucl_object_t *obj = ucl_object_fromstring("hello");

    /* Validate with NULL err -- crashes at ucl_schema.c:994 */
    ucl_object_validate(schema, obj, NULL);

    ucl_object_unref(obj);
    ucl_object_unref(schema);
    return 0;
}

Build and run:

cc -fsanitize=address -g -I include -o repro repro.c build/libucl.a
./repro
# SEGV at ucl_schema.c:994

ASAN output:

==PID==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
==PID==The signal is caused by a WRITE memory access.
    #0 in ucl_schema_validate ucl_schema.c:994
    #1 in ucl_schema_validate ucl_schema.c:955
    #2 in ucl_object_validate_root_ext ucl_schema.c:1095
    #3 in ucl_object_validate ucl_schema.c:1071

Suggested Fix

Add NULL guards at both locations:

// Line 994
if (err != NULL) err->code = UCL_SCHEMA_OK;

// Line 1023
if (err != NULL) err->code = UCL_SCHEMA_OK;

This matches the pattern already used by ucl_schema_create_error() throughout the same file. Happy to send a PR if that helps.

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