diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/bplist.c | 112 |
1 files changed, 68 insertions, 44 deletions
diff --git a/src/bplist.c b/src/bplist.c index 74348b8..0591526 100644 --- a/src/bplist.c +++ b/src/bplist.c @@ -243,6 +243,37 @@ struct bplist_data { plist_err_t err; }; +/* + * Check that len bytes starting at ptr lie within the object area of the + * binary plist, i.e. between the start of the data and the offset table. + * The check compares lengths instead of computing ptr + len, so it never + * forms a pointer outside of the input buffer and cannot wrap around. + */ +static int bplist_object_range_valid(const struct bplist_data *bplist, const char *ptr, uint64_t len) +{ + if (ptr < bplist->data || ptr > bplist->offset_table) { + return 0; + } + return len <= (uint64_t)(bplist->offset_table - ptr); +} + +/* + * Read the object reference at position ref_index of the reference list + * that starts at refs. The offset is computed with overflow checking and + * validated before the reference is read. + */ +static int bplist_read_object_ref(const struct bplist_data *bplist, const char *refs, uint64_t ref_index, uint64_t *ref) +{ + uint64_t ref_offset = 0; + if (uint64_mul_overflow(ref_index, bplist->ref_size, &ref_offset) || + ref_offset > UINT64_MAX - bplist->ref_size || + !bplist_object_range_valid(bplist, refs, ref_offset + bplist->ref_size)) { + return 0; + } + *ref = UINT_TO_HOST(refs + ref_offset, bplist->ref_size); + return 1; +} + #ifdef DEBUG static int plist_bin_debug = 0; #define PLIST_BIN_ERR(...) if (plist_bin_debug) { fprintf(stderr, "libplist[binparser] ERROR: " __VA_ARGS__); } @@ -517,15 +548,12 @@ static plist_t parse_data_node(const char **bnode, uint64_t size) 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(); if (!data) { PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__); return NULL; } - const char *index1_ptr = NULL; - const char *index2_ptr = NULL; data->type = PLIST_DICT; data->length = size; @@ -538,21 +566,14 @@ static plist_t parse_dict_node(struct bplist_data *bplist, const char** bnode, u } 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 > bplist->offset_table) || - (index2_ptr < bplist->data || index2_ptr + bplist->ref_size > bplist->offset_table)) { + /* key references come first, followed by all value references */ + if (!bplist_read_object_ref(bplist, *bnode, j, &index1) || + !bplist_read_object_ref(bplist, *bnode, j + size, &index2)) { plist_free(node); PLIST_BIN_ERR("%s: dict entry %" PRIu64 " is outside of valid range\n", __func__, j); 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); PLIST_BIN_ERR("%s: dict entry %" PRIu64 ": key index (%" PRIu64 ") must be smaller than the number of objects (%" PRIu64 ")\n", __func__, j, index1, bplist->num_objects); @@ -614,14 +635,12 @@ static plist_t parse_dict_node(struct bplist_data *bplist, const char** bnode, u 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(); if (!data) { PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__); return NULL; } - const char *index1_ptr = NULL; data->type = PLIST_ARRAY; data->length = size; @@ -634,17 +653,12 @@ static plist_t parse_array_node(struct bplist_data *bplist, const char** bnode, } 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 > bplist->offset_table) { + if (!bplist_read_object_ref(bplist, *bnode, j, &index1)) { plist_free(node); PLIST_BIN_ERR("%s: array item %" PRIu64 " is outside of valid range\n", __func__, j); return NULL; } - index1 = UINT_TO_HOST(index1_ptr, bplist->ref_size); - if (index1 >= bplist->num_objects) { plist_free(node); PLIST_BIN_ERR("%s: array item %" PRIu64 " object index (%" PRIu64 ") must be smaller than the number of objects (%" PRIu64 ")\n", __func__, j, index1, bplist->num_objects); @@ -694,12 +708,16 @@ static plist_t parse_bin_node(struct bplist_data *bplist, const char** object) { uint16_t type = 0; uint64_t size = 0; - uint64_t pobject = 0; - uint64_t poffset_table = (uint64_t)(uintptr_t)bplist->offset_table; + uint64_t data_size = 0; if (!object) return NULL; + if (!bplist_object_range_valid(bplist, *object, 1)) { + PLIST_BIN_ERR("%s: node marker points outside of valid range\n", __func__); + return NULL; + } + type = (**object) & BPLIST_MASK; size = (**object) & BPLIST_FILL; (*object)++; @@ -713,6 +731,10 @@ static plist_t parse_bin_node(struct bplist_data *bplist, const char** object) case BPLIST_SET: case BPLIST_DICT: { + if (!bplist_object_range_valid(bplist, *object, 1)) { + PLIST_BIN_ERR("%s: size node for node type 0x%02x points outside of valid range\n", __func__, type); + return NULL; + } uint16_t next_size = **object & BPLIST_FILL; if ((**object & BPLIST_MASK) != BPLIST_INT) { PLIST_BIN_ERR("%s: invalid size node type for node type 0x%02x: found 0x%02x, expected 0x%02x\n", __func__, type, **object & BPLIST_MASK, BPLIST_INT); @@ -720,7 +742,7 @@ static plist_t parse_bin_node(struct bplist_data *bplist, const char** object) } (*object)++; next_size = 1 << next_size; - if (*object + next_size > bplist->offset_table) { + if (!bplist_object_range_valid(bplist, *object, next_size)) { PLIST_BIN_ERR("%s: size node data bytes for node type 0x%02x point outside of valid range\n", __func__, type); return NULL; } @@ -733,8 +755,6 @@ static plist_t parse_bin_node(struct bplist_data *bplist, const char** object) } } - pobject = (uint64_t)(uintptr_t)*object; - switch (type) { @@ -785,14 +805,14 @@ static plist_t parse_bin_node(struct bplist_data *bplist, const char** object) } case BPLIST_INT: - if (pobject + (uint64_t)(1 << size) > poffset_table) { + if (!bplist_object_range_valid(bplist, *object, (uint64_t)1 << size)) { PLIST_BIN_ERR("%s: BPLIST_INT data bytes point outside of valid range\n", __func__); return NULL; } return parse_int_node(object, size); case BPLIST_REAL: - if (pobject + (uint64_t)(1 << size) > poffset_table) { + if (!bplist_object_range_valid(bplist, *object, (uint64_t)1 << size)) { PLIST_BIN_ERR("%s: BPLIST_REAL data bytes point outside of valid range\n", __func__); return NULL; } @@ -803,32 +823,32 @@ static plist_t parse_bin_node(struct bplist_data *bplist, const char** object) PLIST_BIN_ERR("%s: invalid data size for BPLIST_DATE node\n", __func__); return NULL; } - if (pobject + (uint64_t)(1 << size) > poffset_table) { + if (!bplist_object_range_valid(bplist, *object, (uint64_t)1 << size)) { PLIST_BIN_ERR("%s: BPLIST_DATE data bytes point outside of valid range\n", __func__); return NULL; } return parse_date_node(object, size); case BPLIST_DATA: - if (pobject + size < pobject || pobject + size > poffset_table) { + if (!bplist_object_range_valid(bplist, *object, size)) { PLIST_BIN_ERR("%s: BPLIST_DATA data bytes point outside of valid range\n", __func__); return NULL; } return parse_data_node(object, size); case BPLIST_STRING: - if (pobject + size < pobject || pobject + size > poffset_table) { + if (!bplist_object_range_valid(bplist, *object, size)) { PLIST_BIN_ERR("%s: BPLIST_STRING data bytes point outside of valid range\n", __func__); return NULL; } return parse_string_node(object, size); case BPLIST_UNICODE: - if (size*2 < size) { + if (uint64_mul_overflow(size, sizeof(uint16_t), &data_size)) { PLIST_BIN_ERR("%s: Integer overflow when calculating BPLIST_UNICODE data size.\n", __func__); return NULL; } - if (pobject + size*2 < pobject || pobject + size*2 > poffset_table) { + if (!bplist_object_range_valid(bplist, *object, data_size)) { PLIST_BIN_ERR("%s: BPLIST_UNICODE data bytes point outside of valid range\n", __func__); return NULL; } @@ -836,21 +856,25 @@ static plist_t parse_bin_node(struct bplist_data *bplist, const char** object) case BPLIST_SET: case BPLIST_ARRAY: - if (pobject + size < pobject || pobject + size > poffset_table) { + /* one object reference per item */ + if (uint64_mul_overflow(size, bplist->ref_size, &data_size) || + !bplist_object_range_valid(bplist, *object, data_size)) { PLIST_BIN_ERR("%s: BPLIST_ARRAY data bytes point outside of valid range\n", __func__); return NULL; } return parse_array_node(bplist, object, size); case BPLIST_UID: - if (pobject + size+1 > poffset_table) { + if (!bplist_object_range_valid(bplist, *object, size + 1)) { PLIST_BIN_ERR("%s: BPLIST_UID data bytes point outside of valid range\n", __func__); return NULL; } return parse_uid_node(object, size); case BPLIST_DICT: - if (pobject + size < pobject || pobject + size > poffset_table) { + /* one key and one value reference per entry */ + if (uint64_mul_overflow(size, 2 * (uint64_t)bplist->ref_size, &data_size) || + !bplist_object_range_valid(bplist, *object, data_size)) { PLIST_BIN_ERR("%s: BPLIST_DICT data bytes point outside of valid range\n", __func__); return NULL; } @@ -876,13 +900,14 @@ static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node return NULL; } - idx_ptr = bplist->offset_table + node_index * bplist->offset_size; - if (idx_ptr < bplist->offset_table || - idx_ptr >= bplist->offset_table + bplist->num_objects * bplist->offset_size) { + /* num_objects * offset_size was checked against the input size in plist_from_bin() */ + uint64_t idx_offset = (uint64_t)node_index * bplist->offset_size; + if (idx_offset >= bplist->num_objects * bplist->offset_size) { PLIST_BIN_ERR("node index %u points outside of valid range\n", node_index); bplist->err = PLIST_ERR_PARSE; return NULL; } + idx_ptr = bplist->offset_table + idx_offset; uint64_t node_offset = UINT_TO_HOST(idx_ptr, bplist->offset_size); if (node_offset > (uint64_t)bplist->size) { @@ -890,13 +915,14 @@ static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node bplist->err = PLIST_ERR_PARSE; return NULL; } - ptr = bplist->data + node_offset; /* make sure the node offset is in a sane range */ - if ((ptr < bplist->data+BPLIST_MAGIC_SIZE+BPLIST_VERSION_SIZE) || (ptr >= bplist->offset_table)) { + if (node_offset < BPLIST_MAGIC_SIZE + BPLIST_VERSION_SIZE || + node_offset >= (uint64_t)(bplist->offset_table - bplist->data)) { PLIST_BIN_ERR("offset for node index %u points outside of valid range\n", node_index); bplist->err = PLIST_ERR_PARSE; return NULL; } + ptr = bplist->data + node_offset; /* Objects can be referenced multiple times, so limit the total number of nodes created to avoid excessive memory use from reference expansion. */ @@ -954,7 +980,6 @@ plist_err_t plist_from_bin(const char *plist_bin, uint32_t length, plist_t * pli uint64_t root_object = 0; const char *offset_table = NULL; uint64_t offset_table_size = 0; - const char *start_data = NULL; const char *end_data = NULL; if (!plist) { @@ -981,7 +1006,6 @@ plist_err_t plist_from_bin(const char *plist_bin, uint32_t length, plist_t * pli return PLIST_ERR_PARSE; } - start_data = plist_bin + BPLIST_MAGIC_SIZE + BPLIST_VERSION_SIZE; end_data = plist_bin + length - sizeof(bplist_trailer_t); //now parse trailer @@ -1020,7 +1044,7 @@ plist_err_t plist_from_bin(const char *plist_bin, uint32_t length, plist_t * pli return PLIST_ERR_PARSE; } - if (offset_table < start_data || offset_table >= end_data) { + if (offset_table_offset < BPLIST_MAGIC_SIZE + BPLIST_VERSION_SIZE || offset_table_offset >= max_valid_offset) { PLIST_BIN_ERR("offset table offset points outside of valid range\n"); return PLIST_ERR_PARSE; } |
