Commit message: bplist: Fix possible out-of-bounds read in parse_array_node() with proper bounds checking
CVE: CVE-2017-5834
CWE: CWE-125
Vulnerability description: The parse_dict_node function in bplist.c in libplist allows attackers to cause a denial of service (out-of-bounds heap read and crash) via a crafted file.

Project: libplist
File: bplist.c

static plist_t parse_uid_node(const char **bnode, uint8_t size)
{
    plist_data_t data = plist_new_plist_data();
    size = size + 1;
    data->intval = UINT_TO_HOST(*bnode, size);
    if (data->intval > UINT32_MAX) {
        free(data);
        return NULL;
    }

    (*bnode) += size;
    data->type = PLIST_UID;
    data->length = sizeof(uint64_t);

    return node_create(NULL, data);
}

Project: libplist
File: bplist.c

typedef struct __attribute__((packed)) {
    uint8_t unused[6];
    uint8_t offset_size;
    uint8_t ref_size;
    uint64_t num_objects;
    uint64_t root_object_index;
    uint64_t offset_table_offset;
} bplist_trailer_t;

Project: libplist
File: bplist.c

static plist_t parse_bin_node(struct bplist_data *bplist, const char** object)
{
    uint16_t type = 0;
    uint64_t size = 0;

    if (!object)
        return NULL;

    type = (**object) & BPLIST_MASK;
    size = (**object) & BPLIST_FILL;
    (*object)++;

    switch (type)
    {

    case BPLIST_NULL:
        switch (size)
        {

        case BPLIST_TRUE:
        {
            plist_data_t data = plist_new_plist_data();
            data->type = PLIST_BOOLEAN;
            data->boolval = TRUE;
            data->length = 1;
            return node_create(NULL, data);
        }

        case BPLIST_FALSE:
        {
            plist_data_t data = plist_new_plist_data();
            data->type = PLIST_BOOLEAN;
            data->boolval = FALSE;
            data->length = 1;
            return node_create(NULL, data);
        }

        case BPLIST_NULL:
        default:
            return NULL;
        }

    case BPLIST_UINT:
        if (*object - bplist->data + (uint64_t)(1 << size) >= bplist->size)
            return NULL;
        return parse_uint_node(object, size);

    case BPLIST_REAL:
        if (*object - bplist->data + (uint64_t)(1 << size) >= bplist->size)
            return NULL;
        return parse_real_node(object, size);

    case BPLIST_DATE:
        if (3 != size)
            return NULL;
        if (*object - bplist->data + (uint64_t)(1 << size) >= bplist->size)
            return NULL;
        return parse_date_node(object, size);

    case BPLIST_DATA:
        if (BPLIST_FILL == size) {
            uint8_t next_size = **object & BPLIST_FILL;
            if ((**object & BPLIST_MASK) != BPLIST_UINT)
                return NULL;
            (*object)++;
            size = UINT_TO_HOST(*object, (1 << next_size));
            (*object) += (1 << next_size);
        }

        if (*object - bplist->data + size >= bplist->size)
            return NULL;
        return parse_data_node(object, size);

    case BPLIST_STRING:
        if (BPLIST_FILL == size) {
            uint8_t next_size = **object & BPLIST_FILL;
            if ((**object & BPLIST_MASK) != BPLIST_UINT)
                return NULL;
            (*object)++;
            size = UINT_TO_HOST(*object, (1 << next_size));
            (*object) += (1 << next_size);
        }

        if (*object - bplist->data + size >= bplist->size)
            return NULL;
        return parse_string_node(object, size);

    case BPLIST_UNICODE:
        if (BPLIST_FILL == size) {
            uint8_t next_size = **object & BPLIST_FILL;
            if ((**object & BPLIST_MASK) != BPLIST_UINT)
                return NULL;
            (*object)++;
            size = UINT_TO_HOST(*object, (1 << next_size));
            (*object) += (1 << next_size);
        }

        if (*object - bplist->data + size * 2 >= bplist->size)
            return NULL;
        return parse_unicode_node(object, size);

    case BPLIST_SET:
    case BPLIST_ARRAY:
        if (BPLIST_FILL == size) {
            uint8_t next_size = **object & BPLIST_FILL;
            if ((**object & BPLIST_MASK) != BPLIST_UINT)
                return NULL;
            (*object)++;
            size = UINT_TO_HOST(*object, (1 << next_size));
            (*object) += (1 << next_size);
        }

        if (*object - bplist->data + size >= bplist->size)
            return NULL;
        return parse_array_node(bplist, object, size);

    case BPLIST_UID:
        return parse_uid_node(object, size);

    case BPLIST_DICT:
        if (BPLIST_FILL == size) {
	    uint8_t next_size = **object & BPLIST_FILL;
            if ((**object & BPLIST_MASK) != BPLIST_UINT)
                return NULL;
            (*object)++;
            size = UINT_TO_HOST(*object, (1 << next_size));
            (*object) += (1 << next_size);
        }

        if (*object - bplist->data + size >= bplist->size)
            return NULL;
        return parse_dict_node(bplist, object, size);
    default:
        return NULL;
    }
    return NULL;
}

Project: libplist
File: bplist.c

PLIST_API void plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist)
{
    bplist_trailer_t *trailer = NULL;
    uint8_t offset_size = 0;
    uint8_t ref_size = 0;
    uint64_t num_objects = 0;
    uint64_t root_object = 0;
    char *offset_table = NULL;

    //first check we have enough data
    if (!(length >= BPLIST_MAGIC_SIZE + BPLIST_VERSION_SIZE + sizeof(bplist_trailer_t)))
        return;
    //check that plist_bin in actually a plist
    if (memcmp(plist_bin, BPLIST_MAGIC, BPLIST_MAGIC_SIZE) != 0)
        return;
    //check for known version
    if (memcmp(plist_bin + BPLIST_MAGIC_SIZE, BPLIST_VERSION, BPLIST_VERSION_SIZE) != 0)
        return;

    //now parse trailer
    trailer = (bplist_trailer_t*)(plist_bin + (length - sizeof(bplist_trailer_t)));

    offset_size = trailer->offset_size;
    ref_size = trailer->ref_size;
    num_objects = be64toh(trailer->num_objects);
    root_object = be64toh(trailer->root_object_index);
    offset_table = (char *)(plist_bin + be64toh(trailer->offset_table_offset));

    if (num_objects == 0)
        return;

    if (offset_size == 0)
        return;

    if (ref_size == 0)
        return;

    if (root_object >= num_objects)
        return;

    if (offset_table < plist_bin || offset_table >= plist_bin + length)
        return;

    if (offset_table + num_objects * offset_size >= plist_bin + length)
        return;

    if (sizeof(uint32_t) * num_objects < num_objects)
        return;

    struct bplist_data bplist;
    bplist.data = plist_bin;
    bplist.size = length;
    bplist.num_objects = num_objects;
    bplist.ref_size = ref_size;
    bplist.offset_size = offset_size;
    bplist.offset_table = offset_table;
    bplist.level = 0;
    bplist.used_indexes = (uint32_t*)malloc(sizeof(uint32_t) * num_objects);

    if (!bplist.used_indexes)
        return;

    *plist = parse_bin_node_at_index(&bplist, root_object);

    free(bplist.used_indexes);
}

Project: libplist
File: bplist.c

static void write_uint(bytearray_t * bplist, uint64_t val)
{
    uint8_t sz = BPLIST_UINT | 4;
    uint64_t zero = 0;

    val = be64toh(val);
    byte_array_append(bplist, &sz, 1);
    byte_array_append(bplist, &zero, sizeof(uint64_t));
    byte_array_append(bplist, &val, sizeof(uint64_t));
}

Project: libplist
File: bplist.c

static void write_uid(bytearray_t * bplist, uint64_t val)
{
    val = (uint32_t)val;
    int size = get_needed_bytes(val);
    uint8_t sz;
    //do not write 3bytes int node
    if (size == 3)
        size++;
    sz = BPLIST_UID | (size-1); // yes, this is what Apple does...

    val = be64toh(val);
    byte_array_append(bplist, &sz, 1);
    byte_array_append(bplist, (uint8_t*)&val + (8-size), size);
}

Project: libplist
File: bplist.c

static plist_t parse_dict_node(struct bplist_data *bplist, const char** bnode, uint64_t size)
{
    uint64_t j;
    uint64_t str_i = 0, str_j = 0;
    uint64_t index1, index2;
    plist_data_t data = plist_new_plist_data();
    const char *const end_data = bplist->data + bplist->size;
    const char *index1_ptr = NULL;
    const char *index2_ptr = NULL;

    data->type = PLIST_DICT;
    data->length = size;

    plist_t node = node_create(NULL, data);

    for (j = 0; j < data->length; j++) {
        str_i = j * bplist->ref_size;
        str_j = (j + size) * bplist->ref_size;
        index1_ptr = (*bnode) + str_i;
        index2_ptr = (*bnode) + str_j;

        if ((index1_ptr < bplist->data || index1_ptr + bplist->ref_size >= end_data) ||
            (index2_ptr < bplist->data || index2_ptr + bplist->ref_size >= end_data)) {
            plist_free(node);
            return NULL;
        }

        index1 = UINT_TO_HOST(index1_ptr, bplist->ref_size);
        index2 = UINT_TO_HOST(index2_ptr, bplist->ref_size);

        if (index1 >= bplist->num_objects) {
            plist_free(node);
            return NULL;
        }
        if (index2 >= bplist->num_objects) {
            plist_free(node);
            return NULL;
        }

        /* process key node */
        plist_t key = parse_bin_node_at_index(bplist, index1);
        if (!key) {
            plist_free(node);
            return NULL;
        }

        if (plist_get_data(key)->type != PLIST_STRING) {
            fprintf(stderr, "ERROR: Malformed binary plist dict, invalid node type for key!\n");
            plist_free(node);
            return NULL;
        }

        /* enforce key type */
        plist_get_data(key)->type = PLIST_KEY;
        if (!plist_get_data(key)->strval) {
            fprintf(stderr, "ERROR: Malformed binary plist dict, invalid key node encountered!\n");
            plist_free(key);
            plist_free(node);
            return NULL;
        }

        /* process value node */
        plist_t val = parse_bin_node_at_index(bplist, index2);
        if (!val) {
            plist_free(key);
            plist_free(node);
            return NULL;
        }

        node_attach(node, key);
        node_attach(node, val);
    }

    return node;
}

Project: libplist
File: bplist.c

PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
{
    ptrarray_t* objects = NULL;
    hashtable_t* ref_table = NULL;
    struct serialize_s ser_s;
    uint8_t offset_size = 0;
    uint8_t ref_size = 0;
    uint64_t num_objects = 0;
    uint64_t root_object = 0;
    uint64_t offset_table_index = 0;
    bytearray_t *bplist_buff = NULL;
    uint64_t i = 0;
    uint8_t *buff = NULL;
    uint64_t *offsets = NULL;
    bplist_trailer_t trailer;
    //for string
    long len = 0;
    long items_read = 0;
    long items_written = 0;
    uint16_t *unicodestr = NULL;
    uint64_t objects_len = 0;
    uint64_t buff_len = 0;

    //check for valid input
    if (!plist || !plist_bin || *plist_bin || !length)
        return;

    //list of objects
    objects = ptr_array_new(256);
    //hashtable to write only once same nodes
    ref_table = hash_table_new(plist_data_hash, plist_data_compare, free);

    //serialize plist
    ser_s.objects = objects;
    ser_s.ref_table = ref_table;
    serialize_plist(plist, &ser_s);

    //now stream to output buffer
    offset_size = 0;			//unknown yet
    objects_len = objects->len;
    ref_size = get_needed_bytes(objects_len);
    num_objects = objects->len;
    root_object = 0;			//root is first in list
    offset_table_index = 0;		//unknown yet

    //setup a dynamic bytes array to store bplist in
    bplist_buff = byte_array_new();

    //set magic number and version
    byte_array_append(bplist_buff, BPLIST_MAGIC, BPLIST_MAGIC_SIZE);
    byte_array_append(bplist_buff, BPLIST_VERSION, BPLIST_VERSION_SIZE);

    //write objects and table
    offsets = (uint64_t *) malloc(num_objects * sizeof(uint64_t));
    for (i = 0; i < num_objects; i++)
    {

        plist_data_t data = plist_get_data(ptr_array_index(objects, i));
        offsets[i] = bplist_buff->len;

        switch (data->type)
        {
        case PLIST_BOOLEAN:
            buff = (uint8_t *) malloc(sizeof(uint8_t));
            buff[0] = data->boolval ? BPLIST_TRUE : BPLIST_FALSE;
            byte_array_append(bplist_buff, buff, sizeof(uint8_t));
            free(buff);
            break;

        case PLIST_UINT:
            if (data->length == 16) {
                write_uint(bplist_buff, data->intval);
            } else {
                write_int(bplist_buff, data->intval);
            }
            break;

        case PLIST_REAL:
            write_real(bplist_buff, data->realval);
            break;

        case PLIST_KEY:
        case PLIST_STRING:
            len = strlen(data->strval);
            if ( is_ascii_string(data->strval, len) )
            {
                write_string(bplist_buff, data->strval);
            }
            else
            {
                unicodestr = plist_utf8_to_utf16(data->strval, len, &items_read, &items_written);
                write_unicode(bplist_buff, unicodestr, items_written);
                free(unicodestr);
            }
            break;
        case PLIST_DATA:
            write_data(bplist_buff, data->buff, data->length);
        case PLIST_ARRAY:
            write_array(bplist_buff, ptr_array_index(objects, i), ref_table, ref_size);
            break;
        case PLIST_DICT:
            write_dict(bplist_buff, ptr_array_index(objects, i), ref_table, ref_size);
            break;
        case PLIST_DATE:
            write_date(bplist_buff, data->realval);
            break;
        case PLIST_UID:
            write_uid(bplist_buff, data->intval);
            break;
        default:
            break;
        }
    }

    //free intermediate objects
    ptr_array_free(objects);
    hash_table_destroy(ref_table);

    //write offsets
    buff_len = bplist_buff->len;
    offset_size = get_needed_bytes(buff_len);
    offset_table_index = bplist_buff->len;
    for (i = 0; i < num_objects; i++) {
        uint64_t offset = be64toh(offsets[i]);
        byte_array_append(bplist_buff, (uint8_t*)&offset + (sizeof(uint64_t) - offset_size), offset_size);
    }
    free(offsets);

    //setup trailer
    memset(trailer.unused, '\0', sizeof(trailer.unused));
    trailer.offset_size = offset_size;
    trailer.ref_size = ref_size;
    trailer.num_objects = be64toh(num_objects);
    trailer.root_object_index = be64toh(root_object);
    trailer.offset_table_offset = be64toh(offset_table_index);

    byte_array_append(bplist_buff, &trailer, sizeof(bplist_trailer_t));

    //set output buffer and size
    *plist_bin = bplist_buff->data;
    *length = bplist_buff->len;

    bplist_buff->data = NULL; // make sure we don't free the output buffer
    byte_array_free(bplist_buff);
}

Project: libplist
File: bplist.c

static void write_int(bytearray_t * bplist, uint64_t val)
{
    int size = get_needed_bytes(val);
    uint8_t sz;
    //do not write 3bytes int node
    if (size == 3)
        size++;
    sz = BPLIST_UINT | Log2(size);

    val = be64toh(val);
    byte_array_append(bplist, &sz, 1);
    byte_array_append(bplist, (uint8_t*)&val + (8-size), size);
}

Project: libplist
File: bplist.c

static plist_t parse_real_node(const char **bnode, uint8_t size)
{
    plist_data_t data = plist_new_plist_data();
    uint8_t buf[8];

    size = 1 << size;			// make length less misleading
    switch (size)
    {
    case sizeof(uint32_t):
        *(uint32_t*)buf = float_bswap32(*(uint32_t*)*bnode);
        data->realval = *(float *) buf;
        break;
    case sizeof(uint64_t):
        *(uint64_t*)buf = float_bswap64(*(uint64_t*)*bnode);
        data->realval = *(double *) buf;
        break;
    default:
        free(data);
        return NULL;
    }
    data->type = PLIST_REAL;
    data->length = sizeof(double);

    return node_create(NULL, data);
}

Project: libplist
File: bplist.c

static plist_t parse_array_node(struct bplist_data *bplist, const char** bnode, uint64_t size)
{
    uint64_t j;
    uint64_t str_j = 0;
    uint64_t index1;
    plist_data_t data = plist_new_plist_data();
    const char *const end_data = bplist->data + bplist->size;
    const char *index1_ptr = NULL;

    data->type = PLIST_ARRAY;
    data->length = size;

    plist_t node = node_create(NULL, data);

    for (j = 0; j < data->length; j++) {
        str_j = j * bplist->ref_size;
        index1_ptr = (*bnode) + str_j;

        if (index1_ptr < bplist->data || index1_ptr + bplist->ref_size >= end_data) {
            plist_free(node);
            return NULL;
        }

        index1 = UINT_TO_HOST(index1_ptr, bplist->ref_size);

        if (index1 >= bplist->num_objects) {
            plist_free(node);
            return NULL;
        }

        /* process value node */
        plist_t val = parse_bin_node_at_index(bplist, index1);
        if (!val) {
            plist_free(node);
            return NULL;
        }

        node_attach(node, val);
    }

    return node;
}

Project: libplist
File: bplist.c

static plist_t parse_uint_node(const char **bnode, uint8_t size)
{
    plist_data_t data = plist_new_plist_data();

    size = 1 << size;			// make length less misleading
    switch (size)
    {
    case sizeof(uint8_t):
    case sizeof(uint16_t):
    case sizeof(uint32_t):
    case sizeof(uint64_t):
        data->length = sizeof(uint64_t);
        break;
    case 16:
        data->length = size;
        break;
    default:
        free(data);
        return NULL;
    };

    data->intval = UINT_TO_HOST(*bnode, size);

    (*bnode) += size;
    data->type = PLIST_UINT;

    return node_create(NULL, data);
}