diff options
| author | 2026-01-17 15:18:06 +0100 | |
|---|---|---|
| committer | 2026-01-17 16:04:00 +0100 | |
| commit | e45099fb21b679aa0cdb0db394587bb5ba675b0c (patch) | |
| tree | b1a2c1dd34877d83a04d6d6fab804fb2e5a65f2d | |
| parent | 26dd27c435a0d110c924e1fef34ad0f6ae60e251 (diff) | |
| download | libplist-e45099fb21b679aa0cdb0db394587bb5ba675b0c.tar.gz libplist-e45099fb21b679aa0cdb0db394587bb5ba675b0c.tar.bz2 | |
Prevent deep nesting of plist structures in all input/output formats
Thanks to @unbengable12 for reporting. Addresses #288, #289, #290, #291, and #292.
| -rw-r--r-- | include/plist/plist.h | 1 | ||||
| -rw-r--r-- | src/bplist.c | 26 | ||||
| -rw-r--r-- | src/jplist.c | 48 | ||||
| -rw-r--r-- | src/oplist.c | 69 | ||||
| -rw-r--r-- | src/out-default.c | 7 | ||||
| -rw-r--r-- | src/out-limd.c | 7 | ||||
| -rw-r--r-- | src/out-plutil.c | 7 | ||||
| -rw-r--r-- | src/plist.h | 4 | ||||
| -rw-r--r-- | src/xplist.c | 111 |
9 files changed, 185 insertions, 95 deletions
diff --git a/include/plist/plist.h b/include/plist/plist.h index 5ea30b8..8ed9063 100644 --- a/include/plist/plist.h +++ b/include/plist/plist.h @@ -145,6 +145,7 @@ extern "C" PLIST_ERR_NO_MEM = -4, /**< not enough memory to handle the operation */ PLIST_ERR_IO = -5, /**< I/O error */ PLIST_ERR_CIRCULAR_REF = -6, /**< circular reference detected */ + PLIST_ERR_MAX_NESTING = -7, /**< maximum nesting depth exceeded */ PLIST_ERR_UNKNOWN = -255 /**< an unspecified error occurred */ } plist_err_t; diff --git a/src/bplist.c b/src/bplist.c index a2dc957..254c0ff 100644 --- a/src/bplist.c +++ b/src/bplist.c @@ -239,6 +239,7 @@ struct bplist_data { const char* offset_table; uint32_t level; ptrarray_t* used_indexes; + plist_err_t err; }; #ifdef DEBUG @@ -787,6 +788,7 @@ static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node if (node_index >= bplist->num_objects) { PLIST_BIN_ERR("node index (%u) must be smaller than the number of objects (%" PRIu64 ")\n", node_index, bplist->num_objects); + bplist->err = PLIST_ERR_PARSE; return NULL; } @@ -794,6 +796,7 @@ static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node if (idx_ptr < bplist->offset_table || idx_ptr >= bplist->offset_table + 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; } @@ -801,6 +804,14 @@ static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node /* make sure the node offset is in a sane range */ if ((ptr < bplist->data+BPLIST_MAGIC_SIZE+BPLIST_VERSION_SIZE) || (ptr >= bplist->offset_table)) { PLIST_BIN_ERR("offset for node index %u points outside of valid range\n", node_index); + bplist->err = PLIST_ERR_PARSE; + return NULL; + } + + /* check nesting depth */ + if (bplist->level > PLIST_MAX_NESTING_DEPTH) { + PLIST_BIN_ERR("maximum nesting depth (%u) exceeded\n",(unsigned)PLIST_MAX_NESTING_DEPTH); + bplist->err = PLIST_ERR_MAX_NESTING; return NULL; } @@ -820,6 +831,7 @@ static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node void *node_level = ptr_array_index(bplist->used_indexes, bplist->level); if (node_i == node_level) { PLIST_BIN_ERR("recursion detected in binary plist\n"); + bplist->err = PLIST_ERR_PARSE; return NULL; } } @@ -931,6 +943,7 @@ plist_err_t plist_from_bin(const char *plist_bin, uint32_t length, plist_t * pli bplist.offset_table = offset_table; bplist.level = 0; bplist.used_indexes = ptr_array_new(16); + bplist.err = PLIST_ERR_SUCCESS; if (!bplist.used_indexes) { PLIST_BIN_ERR("failed to create array to hold used node indexes. Out of memory?\n"); @@ -942,7 +955,7 @@ plist_err_t plist_from_bin(const char *plist_bin, uint32_t length, plist_t * pli ptr_array_free(bplist.used_indexes); if (!*plist) { - return PLIST_ERR_PARSE; + return (bplist.err != PLIST_ERR_SUCCESS) ? bplist.err : PLIST_ERR_PARSE; } return PLIST_ERR_SUCCESS; @@ -1002,11 +1015,16 @@ struct serialize_s hashtable_t* in_stack; }; -static plist_err_t serialize_plist(node_t node, void* data) +static plist_err_t serialize_plist(node_t node, void* data, uint32_t depth) { uint64_t *index_val = NULL; struct serialize_s *ser = (struct serialize_s *) data; + if (depth > PLIST_MAX_NESTING_DEPTH) { + PLIST_BIN_WRITE_ERR("maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH); + return PLIST_ERR_MAX_NESTING; + } + // circular reference check: is node on current recursion stack? if (hash_table_lookup(ser->in_stack, node)) { PLIST_BIN_WRITE_ERR("circular reference detected\n"); @@ -1036,7 +1054,7 @@ static plist_err_t serialize_plist(node_t node, void* data) node_t ch; plist_err_t err = PLIST_ERR_SUCCESS; for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) { - err = serialize_plist(ch, data); + err = serialize_plist(ch, data, depth+1); if (err != PLIST_ERR_SUCCESS) { break; } @@ -1313,7 +1331,7 @@ plist_err_t plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length) ser_s.objects = objects; ser_s.ref_table = ref_table; ser_s.in_stack = in_stack; - plist_err_t err = serialize_plist((node_t)plist, &ser_s); + plist_err_t err = serialize_plist((node_t)plist, &ser_s, 0); if (err != PLIST_ERR_SUCCESS) { ptr_array_free(objects); hash_table_destroy(ref_table); diff --git a/src/jplist.c b/src/jplist.c index 2e53400..9a40844 100644 --- a/src/jplist.c +++ b/src/jplist.c @@ -323,6 +323,11 @@ static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t dep return PLIST_ERR_INVALID_ARG; } + if (depth > PLIST_MAX_NESTING_DEPTH) { + PLIST_JSON_WRITE_ERR("maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH); + return PLIST_ERR_MAX_NESTING; + } + if (hash_table_lookup(visited, node)) { PLIST_JSON_WRITE_ERR("circular reference detected\n"); return PLIST_ERR_CIRCULAR_REF; @@ -471,6 +476,7 @@ plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, in typedef struct { jsmntok_t* tokens; int count; + plist_err_t err; } jsmntok_info_t; static int64_t parse_decimal(const char* str, const char* str_end, char** endp) @@ -698,12 +704,18 @@ static plist_t parse_string(const char* js, jsmntok_info_t* ti, int* index) return node; } -static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index); +static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index, uint32_t depth); -static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index) +static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index, uint32_t depth) { if (ti->tokens[*index].type != JSMN_ARRAY) { PLIST_JSON_ERR("%s: token type != JSMN_ARRAY\n", __func__); + ti->err = PLIST_ERR_PARSE; + return NULL; + } + if (depth > PLIST_MAX_NESTING_DEPTH) { + PLIST_JSON_ERR("%s: maximum nesting depth (%u) exceeded\n", __func__, (unsigned)PLIST_MAX_NESTING_DEPTH); + ti->err = PLIST_ERR_MAX_NESTING; return NULL; } plist_t arr = plist_new_array(); @@ -714,15 +726,16 @@ static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index) if (j >= ti->count) { PLIST_JSON_ERR("%s: token index out of valid range\n", __func__); plist_free(arr); + ti->err = PLIST_ERR_PARSE; return NULL; } plist_t val = NULL; switch (ti->tokens[j].type) { case JSMN_OBJECT: - val = parse_object(js, ti, &j); + val = parse_object(js, ti, &j, depth+1); break; case JSMN_ARRAY: - val = parse_array(js, ti, &j); + val = parse_array(js, ti, &j, depth+1); break; case JSMN_STRING: val = parse_string(js, ti, &j); @@ -737,6 +750,7 @@ static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index) plist_array_append_item(arr, val); } else { plist_free(arr); + ti->err = PLIST_ERR_PARSE; return NULL; } } @@ -744,10 +758,16 @@ static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index) return arr; } -static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index) +static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index, uint32_t depth) { if (ti->tokens[*index].type != JSMN_OBJECT) { PLIST_JSON_ERR("%s: token type != JSMN_OBJECT\n", __func__); + ti->err = PLIST_ERR_PARSE; + return NULL; + } + if (depth > PLIST_MAX_NESTING_DEPTH) { + PLIST_JSON_ERR("%s: maximum nesting depth (%u) exceeded\n", __func__, (unsigned)PLIST_MAX_NESTING_DEPTH); + ti->err = PLIST_ERR_MAX_NESTING; return NULL; } int num_tokens = ti->tokens[*index].size; @@ -755,6 +775,7 @@ static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index) int j = (*index)+1; if (num_tokens % 2 != 0) { PLIST_JSON_ERR("%s: number of children must be even\n", __func__); + ti->err = PLIST_ERR_PARSE; return NULL; } plist_t obj = plist_new_dict(); @@ -762,12 +783,14 @@ static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index) if (j+1 >= ti->count) { PLIST_JSON_ERR("%s: token index out of valid range\n", __func__); plist_free(obj); + ti->err = PLIST_ERR_PARSE; return NULL; } if (ti->tokens[j].type == JSMN_STRING) { char* key = unescape_string(js + ti->tokens[j].start, ti->tokens[j].end - ti->tokens[j].start, NULL); if (!key) { plist_free(obj); + ti->err = PLIST_ERR_PARSE; return NULL; } plist_t val = NULL; @@ -775,10 +798,10 @@ static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index) num++; switch (ti->tokens[j].type) { case JSMN_OBJECT: - val = parse_object(js, ti, &j); + val = parse_object(js, ti, &j, depth+1); break; case JSMN_ARRAY: - val = parse_array(js, ti, &j); + val = parse_array(js, ti, &j, depth+1); break; case JSMN_STRING: val = parse_string(js, ti, &j); @@ -794,12 +817,14 @@ static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index) } else { free(key); plist_free(obj); + ti->err = PLIST_ERR_PARSE; return NULL; } free(key); } else { PLIST_JSON_ERR("%s: keys must be of type STRING\n", __func__); plist_free(obj); + ti->err = PLIST_ERR_PARSE; return NULL; } } @@ -859,7 +884,7 @@ plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist) } int startindex = 0; - jsmntok_info_t ti = { tokens, parser.toknext }; + jsmntok_info_t ti = { tokens, parser.toknext, PLIST_ERR_SUCCESS }; switch (tokens[startindex].type) { case JSMN_PRIMITIVE: *plist = parse_primitive(json, &ti, &startindex); @@ -868,14 +893,17 @@ plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist) *plist = parse_string(json, &ti, &startindex); break; case JSMN_ARRAY: - *plist = parse_array(json, &ti, &startindex); + *plist = parse_array(json, &ti, &startindex, 0); break; case JSMN_OBJECT: - *plist = parse_object(json, &ti, &startindex); + *plist = parse_object(json, &ti, &startindex, 0); break; default: break; } free(tokens); + if (!*plist) { + return (ti.err != PLIST_ERR_SUCCESS) ? ti.err : PLIST_ERR_PARSE; + } return PLIST_ERR_SUCCESS; } diff --git a/src/oplist.c b/src/oplist.c index 680873c..0eea27a 100644 --- a/src/oplist.c +++ b/src/oplist.c @@ -367,6 +367,11 @@ static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t dep return PLIST_ERR_INVALID_ARG; } + if (depth > PLIST_MAX_NESTING_DEPTH) { + PLIST_OSTEP_WRITE_ERR("node tree is nested too deeply\n"); + return PLIST_ERR_MAX_NESTING; + } + if (hash_table_lookup(visited, node)) { PLIST_OSTEP_WRITE_ERR("circular reference detected\n"); return PLIST_ERR_CIRCULAR_REF; @@ -511,7 +516,7 @@ struct _parse_ctx { const char *start; const char *pos; const char *end; - int err; + plist_err_t err; uint32_t depth; }; typedef struct _parse_ctx* parse_ctx; @@ -568,50 +573,50 @@ static void parse_dict_data(parse_ctx ctx, plist_t dict) } key = NULL; ctx->err = node_from_openstep(ctx, &key); - if (ctx->err != 0) { + if (ctx->err != PLIST_ERR_SUCCESS) { break; } if (!PLIST_IS_STRING(key)) { PLIST_OSTEP_ERR("Invalid type for dictionary key at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } parse_skip_ws(ctx); if (ctx->pos >= ctx->end) { PLIST_OSTEP_ERR("EOF while parsing dictionary '=' delimiter at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } if (*ctx->pos != '=') { PLIST_OSTEP_ERR("Missing '=' while parsing dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } ctx->pos++; if (ctx->pos >= ctx->end) { PLIST_OSTEP_ERR("EOF while parsing dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } val = NULL; ctx->err = node_from_openstep(ctx, &val); - if (ctx->err != 0) { + if (ctx->err != PLIST_ERR_SUCCESS) { break; } if (!val) { PLIST_OSTEP_ERR("Missing value for dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } parse_skip_ws(ctx); if (ctx->pos >= ctx->end) { PLIST_OSTEP_ERR("EOF while parsing dictionary item terminator ';' at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } if (*ctx->pos != ';') { PLIST_OSTEP_ERR("Missing terminating ';' while parsing dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } @@ -631,10 +636,10 @@ static plist_err_t node_from_openstep(parse_ctx ctx, plist_t *plist) plist_t subnode = NULL; const char *p = NULL; ctx->depth++; - if (ctx->depth > 1000) { + if (ctx->depth > PLIST_MAX_NESTING_DEPTH) { PLIST_OSTEP_ERR("Too many levels of recursion (%u) at offset %ld\n", ctx->depth, (long int)(ctx->pos - ctx->start)); - ctx->err++; - return PLIST_ERR_PARSE; + ctx->err = PLIST_ERR_MAX_NESTING; + return ctx->err; } while (ctx->pos < ctx->end && !ctx->err) { parse_skip_ws(ctx); @@ -652,12 +657,12 @@ static plist_err_t node_from_openstep(parse_ctx ctx, plist_t *plist) } if (ctx->pos >= ctx->end) { PLIST_OSTEP_ERR("EOF while parsing dictionary terminator '}' at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } if (*ctx->pos != '}') { PLIST_OSTEP_ERR("Missing terminating '}' at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } ctx->pos++; @@ -675,11 +680,11 @@ static plist_err_t node_from_openstep(parse_ctx ctx, plist_t *plist) break; } ctx->err = node_from_openstep(ctx, &tmp); - if (ctx->err != 0) { + if (ctx->err != PLIST_ERR_SUCCESS) { break; } if (!tmp) { - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } plist_array_append_item(subnode, tmp); @@ -687,7 +692,7 @@ static plist_err_t node_from_openstep(parse_ctx ctx, plist_t *plist) parse_skip_ws(ctx); if (ctx->pos >= ctx->end) { PLIST_OSTEP_ERR("EOF while parsing array item delimiter ',' at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } if (*ctx->pos != ',') { @@ -697,17 +702,17 @@ static plist_err_t node_from_openstep(parse_ctx ctx, plist_t *plist) } plist_free(tmp); tmp = NULL; - if (ctx->err) { + if (ctx->err != PLIST_ERR_SUCCESS) { goto err_out; } if (ctx->pos >= ctx->end) { PLIST_OSTEP_ERR("EOF while parsing array terminator ')' at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } if (*ctx->pos != ')') { PLIST_OSTEP_ERR("Missing terminating ')' at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } ctx->pos++; @@ -722,7 +727,7 @@ static plist_err_t node_from_openstep(parse_ctx ctx, plist_t *plist) parse_skip_ws(ctx); if (ctx->pos >= ctx->end) { PLIST_OSTEP_ERR("EOF while parsing data terminator '>' at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } if (*ctx->pos == '>') { @@ -730,19 +735,19 @@ static plist_err_t node_from_openstep(parse_ctx ctx, plist_t *plist) } if (!isxdigit(*ctx->pos)) { PLIST_OSTEP_ERR("Invalid byte group in data at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } uint8_t b = HEX_DIGIT(*ctx->pos); ctx->pos++; if (ctx->pos >= ctx->end) { PLIST_OSTEP_ERR("Unexpected end of data at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } if (!isxdigit(*ctx->pos)) { PLIST_OSTEP_ERR("Invalid byte group in data at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } b = (b << 4) + HEX_DIGIT(*ctx->pos); @@ -758,14 +763,14 @@ static plist_err_t node_from_openstep(parse_ctx ctx, plist_t *plist) byte_array_free(bytes); plist_free_data(data); PLIST_OSTEP_ERR("EOF while parsing data terminator '>' at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } if (*ctx->pos != '>') { byte_array_free(bytes); plist_free_data(data); PLIST_OSTEP_ERR("Missing terminating '>' at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } ctx->pos++; @@ -793,13 +798,13 @@ static plist_err_t node_from_openstep(parse_ctx ctx, plist_t *plist) if (ctx->pos >= ctx->end) { plist_free_data(data); PLIST_OSTEP_ERR("EOF while parsing quoted string at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } if (*ctx->pos != c) { plist_free_data(data); PLIST_OSTEP_ERR("Missing closing quote (%c) at offset %ld\n", c, (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } size_t slen = ctx->pos - p; @@ -900,7 +905,7 @@ static plist_err_t node_from_openstep(parse_ctx ctx, plist_t *plist) } else { plist_free_data(data); PLIST_OSTEP_ERR("Unexpected character when parsing unquoted string at offset %ld\n", (long int)(ctx->pos - ctx->start)); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; break; } } @@ -909,11 +914,11 @@ static plist_err_t node_from_openstep(parse_ctx ctx, plist_t *plist) ctx->depth--; err_out: - if (ctx->err) { + if (ctx->err != PLIST_ERR_SUCCESS) { plist_free(subnode); plist_free(*plist); *plist = NULL; - return PLIST_ERR_PARSE; + return ctx->err; } return PLIST_ERR_SUCCESS; } diff --git a/src/out-default.c b/src/out-default.c index 09e64c3..fb57bcf 100644 --- a/src/out-default.c +++ b/src/out-default.c @@ -318,6 +318,13 @@ static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t dep return PLIST_ERR_INVALID_ARG; } + if (depth > PLIST_MAX_NESTING_DEPTH) { +#if DEBUG + fprintf(stderr, "libplist: ERROR: maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH); +#endif + return PLIST_ERR_MAX_NESTING; + } + if (hash_table_lookup(visited, node)) { #if DEBUG fprintf(stderr, "libplist: ERROR: circular reference detected\n"); diff --git a/src/out-limd.c b/src/out-limd.c index e281644..35247fb 100644 --- a/src/out-limd.c +++ b/src/out-limd.c @@ -286,6 +286,13 @@ static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t dep return PLIST_ERR_INVALID_ARG; } + if (depth > PLIST_MAX_NESTING_DEPTH) { +#if DEBUG + fprintf(stderr, "libplist: ERROR: maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH); +#endif + return PLIST_ERR_MAX_NESTING; + } + if (hash_table_lookup(visited, node)) { #if DEBUG fprintf(stderr, "libplist: ERROR: circular reference detected\n"); diff --git a/src/out-plutil.c b/src/out-plutil.c index 5354aa3..bf9e72e 100644 --- a/src/out-plutil.c +++ b/src/out-plutil.c @@ -312,6 +312,13 @@ static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t dep return PLIST_ERR_INVALID_ARG; } + if (depth > PLIST_MAX_NESTING_DEPTH) { +#if DEBUG + fprintf(stderr, "libplist: ERROR: maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH); +#endif + return PLIST_ERR_MAX_NESTING; + } + if (hash_table_lookup(visited, node)) { #if DEBUG fprintf(stderr, "libplist: ERROR: circular reference detected\n"); diff --git a/src/plist.h b/src/plist.h index 3043fb7..e310bcc 100644 --- a/src/plist.h +++ b/src/plist.h @@ -49,6 +49,10 @@ #endif #endif +#ifndef PLIST_MAX_NESTING_DEPTH +#define PLIST_MAX_NESTING_DEPTH 512 +#endif + #include "plist/plist.h" struct plist_data_s diff --git a/src/xplist.c b/src/xplist.c index c25c6b9..9870104 100644 --- a/src/xplist.c +++ b/src/xplist.c @@ -452,6 +452,11 @@ static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t dep return PLIST_ERR_INVALID_ARG; } + if (depth > PLIST_MAX_NESTING_DEPTH) { + PLIST_XML_WRITE_ERR("maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH); + return PLIST_ERR_MAX_NESTING; + } + if (hash_table_lookup(visited, node)) { PLIST_XML_WRITE_ERR("circular reference detected\n"); return PLIST_ERR_CIRCULAR_REF; @@ -596,7 +601,7 @@ plist_err_t plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length) struct _parse_ctx { const char *pos; const char *end; - int err; + plist_err_t err; }; typedef struct _parse_ctx* parse_ctx; @@ -720,21 +725,21 @@ static text_part_t* get_text_parts(parse_ctx ctx, const char* tag, size_t tag_le find_char(ctx, '<', 0); if (ctx->pos >= ctx->end || *ctx->pos != '<') { PLIST_XML_ERR("EOF while looking for closing tag\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; return NULL; } q = ctx->pos; ctx->pos++; if (ctx->pos >= ctx->end) { PLIST_XML_ERR("EOF while parsing '%s'\n", p); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; return NULL; } if (*ctx->pos == '!') { ctx->pos++; if (ctx->pos >= ctx->end-1) { PLIST_XML_ERR("EOF while parsing <! special tag\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; return NULL; } if (*ctx->pos == '-' && *(ctx->pos+1) == '-') { @@ -747,7 +752,7 @@ static text_part_t* get_text_parts(parse_ctx ctx, const char* tag, size_t tag_le find_str(ctx, "-->", 3, 0); if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "-->", 3) != 0) { PLIST_XML_ERR("EOF while looking for end of comment\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; return NULL; } ctx->pos += 3; @@ -755,7 +760,7 @@ static text_part_t* get_text_parts(parse_ctx ctx, const char* tag, size_t tag_le ctx->pos++; if (ctx->pos >= ctx->end - 8) { PLIST_XML_ERR("EOF while parsing <[ tag\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; return NULL; } if (strncmp(ctx->pos, "CDATA[", 6) == 0) { @@ -771,7 +776,7 @@ static text_part_t* get_text_parts(parse_ctx ctx, const char* tag, size_t tag_le find_str(ctx, "]]>", 3, 0); if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "]]>", 3) != 0) { PLIST_XML_ERR("EOF while looking for end of CDATA block\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; return NULL; } q = ctx->pos; @@ -785,14 +790,14 @@ static text_part_t* get_text_parts(parse_ctx ctx, const char* tag, size_t tag_le p = ctx->pos; find_next(ctx, " \r\n\t>", 5, 1); PLIST_XML_ERR("Invalid special tag <[%.*s> encountered inside <%s> tag\n", (int)(ctx->pos - p), p, tag); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; return NULL; } } else { p = ctx->pos; find_next(ctx, " \r\n\t>", 5, 1); PLIST_XML_ERR("Invalid special tag <!%.*s> encountered inside <%s> tag\n", (int)(ctx->pos - p), p, tag); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; return NULL; } } else if (*ctx->pos == '/') { @@ -801,25 +806,25 @@ static text_part_t* get_text_parts(parse_ctx ctx, const char* tag, size_t tag_le p = ctx->pos; find_next(ctx, " \r\n\t>", 5, 1); PLIST_XML_ERR("Invalid tag <%.*s> encountered inside <%s> tag\n", (int)(ctx->pos - p), p, tag); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; return NULL; } } while (1); ctx->pos++; if (ctx->pos >= ctx->end-tag_len || strncmp(ctx->pos, tag, tag_len) != 0) { PLIST_XML_ERR("EOF or end tag mismatch\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; return NULL; } ctx->pos+=tag_len; parse_skip_ws(ctx); if (ctx->pos >= ctx->end) { PLIST_XML_ERR("EOF while parsing closing tag\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; return NULL; } else if (*ctx->pos != '>') { PLIST_XML_ERR("Invalid closing tag; expected '>', found '%c'\n", *ctx->pos); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; return NULL; } ctx->pos++; @@ -995,6 +1000,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) void *prev; }; struct node_path_item* node_path = NULL; + int depth = 0; while (ctx->pos < ctx->end && !ctx->err) { parse_skip_ws(ctx); @@ -1005,13 +1011,13 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) p = ctx->pos; find_next(ctx, " \t\r\n", 4, 0); PLIST_XML_ERR("Expected: opening tag, found: %.*s\n", (int)(ctx->pos - p), p); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } ctx->pos++; if (ctx->pos >= ctx->end) { PLIST_XML_ERR("EOF while parsing tag\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } @@ -1019,12 +1025,12 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) find_str(ctx, "?>", 2, 1); if (ctx->pos > ctx->end-2) { PLIST_XML_ERR("EOF while looking for <? tag closing marker\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } if (strncmp(ctx->pos, "?>", 2) != 0) { PLIST_XML_ERR("Couldn't find <? tag closing marker\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } ctx->pos += 2; @@ -1036,7 +1042,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) find_str(ctx,"-->", 3, 0); if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "-->", 3) != 0) { PLIST_XML_ERR("Couldn't find end of comment\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } ctx->pos+=3; @@ -1047,7 +1053,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) find_next(ctx, " \t\r\n[>", 6, 1); if (ctx->pos >= ctx->end) { PLIST_XML_ERR("EOF while parsing !DOCTYPE\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } if (*ctx->pos == '[') { @@ -1065,7 +1071,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) find_str(ctx, "]>", 2, 1); if (ctx->pos > ctx->end-2 || strncmp(ctx->pos, "]>", 2) != 0) { PLIST_XML_ERR("Couldn't find end of DOCTYPE\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } ctx->pos += 2; @@ -1074,7 +1080,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) p = ctx->pos; find_next(ctx, " \r\n\t>", 5, 1); PLIST_XML_ERR("Invalid or incomplete special tag <%.*s> encountered\n", (int)(ctx->pos - p), p); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } continue; @@ -1085,7 +1091,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) find_next(ctx," \r\n\t<>", 6, 0); if (ctx->pos >= ctx->end) { PLIST_XML_ERR("Unexpected EOF while parsing XML\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } size_t taglen = ctx->pos - p; @@ -1097,12 +1103,12 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) } if (ctx->pos >= ctx->end) { PLIST_XML_ERR("Unexpected EOF while parsing XML\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } if (*ctx->pos != '>') { PLIST_XML_ERR("Missing '>' for tag <%s\n", tag); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } if (*(ctx->pos-1) == '/') { @@ -1123,14 +1129,14 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) } if (is_empty) { PLIST_XML_ERR("Empty plist tag\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } struct node_path_item *path_item = (struct node_path_item*)malloc(sizeof(struct node_path_item)); if (!path_item) { PLIST_XML_ERR("out of memory when allocating node path item\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } path_item->type = "plist"; @@ -1141,17 +1147,17 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) } else if (!strcmp(tag, "/plist")) { if (!has_content) { PLIST_XML_ERR("encountered empty plist tag\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } if (!node_path) { PLIST_XML_ERR("node path is empty while trying to match closing tag with opening tag\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } if (strcmp(node_path->type, tag+1) != 0) { PLIST_XML_ERR("mismatching closing tag <%s> found for opening tag <%s>\n", tag, node_path->type); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } struct node_path_item *path_item = node_path; @@ -1179,7 +1185,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) if (!tp) { PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag); text_parts_free((text_part_t*)first_part.next); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } if (tp->begin) { @@ -1188,7 +1194,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) if (!str_content) { PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); text_parts_free((text_part_t*)first_part.next); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } char *str = str_content; @@ -1230,7 +1236,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) if (!tp) { PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag); text_parts_free((text_part_t*)first_part.next); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } if (tp->begin) { @@ -1239,7 +1245,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) if (!str_content) { PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); text_parts_free((text_part_t*)first_part.next); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } data->realval = atof(str_content); @@ -1274,14 +1280,14 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) if (!tp) { PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag); text_parts_free((text_part_t*)first_part.next); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } str = text_parts_get_content(tp, 1, &length, NULL); text_parts_free((text_part_t*)first_part.next); if (!str) { PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } if (!strcmp(tag, "key") && !keyname && parent && (plist_get_node_type(parent) == PLIST_DICT)) { @@ -1315,7 +1321,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) if (!tp) { PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag); text_parts_free((text_part_t*)first_part.next); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } if (tp->begin) { @@ -1324,7 +1330,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) if (!str_content) { PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); text_parts_free((text_part_t*)first_part.next); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } size_t size = tp->length; @@ -1347,7 +1353,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) if (!tp) { PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag); text_parts_free((text_part_t*)first_part.next); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } Time64_T timev = 0; @@ -1358,7 +1364,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) if (!str_content) { PLIST_XML_ERR("Could not get text content for '%s' node\n", tag); text_parts_free((text_part_t*)first_part.next); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } @@ -1387,7 +1393,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) } else { PLIST_XML_ERR("Unexpected tag <%s%s> encountered\n", tag, (is_empty) ? "/" : ""); ctx->pos = ctx->end; - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } if (subnode && !closing_tag) { @@ -1405,7 +1411,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) case PLIST_DICT: if (!keyname) { PLIST_XML_ERR("missing key name while adding dict item\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } plist_dict_set_item(parent, keyname, subnode); @@ -1416,35 +1422,42 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) default: /* should not happen */ PLIST_XML_ERR("parent is not a structured node\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } } if (!is_empty && (data->type == PLIST_DICT || data->type == PLIST_ARRAY)) { + if (depth >= PLIST_MAX_NESTING_DEPTH) { + PLIST_XML_ERR("maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH); + ctx->err = PLIST_ERR_MAX_NESTING; + goto err_out; + } struct node_path_item *path_item = (struct node_path_item*)malloc(sizeof(struct node_path_item)); if (!path_item) { PLIST_XML_ERR("out of memory when allocating node path item\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } path_item->type = (data->type == PLIST_DICT) ? XPLIST_DICT : XPLIST_ARRAY; path_item->prev = node_path; node_path = path_item; + depth++; parent = subnode; } subnode = NULL; } else if (closing_tag) { if (!node_path) { PLIST_XML_ERR("node path is empty while trying to match closing tag with opening tag\n"); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } if (strcmp(node_path->type, tag+1) != 0) { PLIST_XML_ERR("unexpected %s found (for opening %s)\n", tag, node_path->type); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; goto err_out; } + if (depth > 0) depth--; struct node_path_item *path_item = node_path; node_path = (struct node_path_item*)node_path->prev; free(path_item); @@ -1466,7 +1479,7 @@ static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist) if (node_path) { PLIST_XML_ERR("EOF encountered while </%s> was expected\n", node_path->type); - ctx->err++; + ctx->err = PLIST_ERR_PARSE; } err_out: @@ -1481,10 +1494,10 @@ err_out: free(path_item); } - if (ctx->err) { + if (ctx->err != PLIST_ERR_SUCCESS) { plist_free(*plist); *plist = NULL; - return PLIST_ERR_PARSE; + return ctx->err; } /* check if we have a UID "dict" so we can replace it with a proper UID node */ @@ -1511,7 +1524,7 @@ plist_err_t plist_from_xml(const char *plist_xml, uint32_t length, plist_t * pli return PLIST_ERR_INVALID_ARG; } - struct _parse_ctx ctx = { plist_xml, plist_xml + length, 0 }; + struct _parse_ctx ctx = { plist_xml, plist_xml + length, PLIST_ERR_SUCCESS }; return node_from_xml(&ctx, plist); } |
