diff options
| author | 2026-01-17 15:18:06 +0100 | |
|---|---|---|
| committer | 2026-01-17 16:04:00 +0100 | |
| commit | e45099fb21b679aa0cdb0db394587bb5ba675b0c (patch) | |
| tree | b1a2c1dd34877d83a04d6d6fab804fb2e5a65f2d /src/jplist.c | |
| 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.
Diffstat (limited to 'src/jplist.c')
| -rw-r--r-- | src/jplist.c | 48 |
1 files changed, 38 insertions, 10 deletions
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 | |||
| 323 | return PLIST_ERR_INVALID_ARG; | 323 | return PLIST_ERR_INVALID_ARG; |
| 324 | } | 324 | } |
| 325 | 325 | ||
| 326 | if (depth > PLIST_MAX_NESTING_DEPTH) { | ||
| 327 | PLIST_JSON_WRITE_ERR("maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH); | ||
| 328 | return PLIST_ERR_MAX_NESTING; | ||
| 329 | } | ||
| 330 | |||
| 326 | if (hash_table_lookup(visited, node)) { | 331 | if (hash_table_lookup(visited, node)) { |
| 327 | PLIST_JSON_WRITE_ERR("circular reference detected\n"); | 332 | PLIST_JSON_WRITE_ERR("circular reference detected\n"); |
| 328 | return PLIST_ERR_CIRCULAR_REF; | 333 | 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 | |||
| 471 | typedef struct { | 476 | typedef struct { |
| 472 | jsmntok_t* tokens; | 477 | jsmntok_t* tokens; |
| 473 | int count; | 478 | int count; |
| 479 | plist_err_t err; | ||
| 474 | } jsmntok_info_t; | 480 | } jsmntok_info_t; |
| 475 | 481 | ||
| 476 | static int64_t parse_decimal(const char* str, const char* str_end, char** endp) | 482 | 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) | |||
| 698 | return node; | 704 | return node; |
| 699 | } | 705 | } |
| 700 | 706 | ||
| 701 | static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index); | 707 | static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index, uint32_t depth); |
| 702 | 708 | ||
| 703 | static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index) | 709 | static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index, uint32_t depth) |
| 704 | { | 710 | { |
| 705 | if (ti->tokens[*index].type != JSMN_ARRAY) { | 711 | if (ti->tokens[*index].type != JSMN_ARRAY) { |
| 706 | PLIST_JSON_ERR("%s: token type != JSMN_ARRAY\n", __func__); | 712 | PLIST_JSON_ERR("%s: token type != JSMN_ARRAY\n", __func__); |
| 713 | ti->err = PLIST_ERR_PARSE; | ||
| 714 | return NULL; | ||
| 715 | } | ||
| 716 | if (depth > PLIST_MAX_NESTING_DEPTH) { | ||
| 717 | PLIST_JSON_ERR("%s: maximum nesting depth (%u) exceeded\n", __func__, (unsigned)PLIST_MAX_NESTING_DEPTH); | ||
| 718 | ti->err = PLIST_ERR_MAX_NESTING; | ||
| 707 | return NULL; | 719 | return NULL; |
| 708 | } | 720 | } |
| 709 | plist_t arr = plist_new_array(); | 721 | plist_t arr = plist_new_array(); |
| @@ -714,15 +726,16 @@ static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index) | |||
| 714 | if (j >= ti->count) { | 726 | if (j >= ti->count) { |
| 715 | PLIST_JSON_ERR("%s: token index out of valid range\n", __func__); | 727 | PLIST_JSON_ERR("%s: token index out of valid range\n", __func__); |
| 716 | plist_free(arr); | 728 | plist_free(arr); |
| 729 | ti->err = PLIST_ERR_PARSE; | ||
| 717 | return NULL; | 730 | return NULL; |
| 718 | } | 731 | } |
| 719 | plist_t val = NULL; | 732 | plist_t val = NULL; |
| 720 | switch (ti->tokens[j].type) { | 733 | switch (ti->tokens[j].type) { |
| 721 | case JSMN_OBJECT: | 734 | case JSMN_OBJECT: |
| 722 | val = parse_object(js, ti, &j); | 735 | val = parse_object(js, ti, &j, depth+1); |
| 723 | break; | 736 | break; |
| 724 | case JSMN_ARRAY: | 737 | case JSMN_ARRAY: |
| 725 | val = parse_array(js, ti, &j); | 738 | val = parse_array(js, ti, &j, depth+1); |
| 726 | break; | 739 | break; |
| 727 | case JSMN_STRING: | 740 | case JSMN_STRING: |
| 728 | val = parse_string(js, ti, &j); | 741 | val = parse_string(js, ti, &j); |
| @@ -737,6 +750,7 @@ static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index) | |||
| 737 | plist_array_append_item(arr, val); | 750 | plist_array_append_item(arr, val); |
| 738 | } else { | 751 | } else { |
| 739 | plist_free(arr); | 752 | plist_free(arr); |
| 753 | ti->err = PLIST_ERR_PARSE; | ||
| 740 | return NULL; | 754 | return NULL; |
| 741 | } | 755 | } |
| 742 | } | 756 | } |
| @@ -744,10 +758,16 @@ static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index) | |||
| 744 | return arr; | 758 | return arr; |
| 745 | } | 759 | } |
| 746 | 760 | ||
| 747 | static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index) | 761 | static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index, uint32_t depth) |
| 748 | { | 762 | { |
| 749 | if (ti->tokens[*index].type != JSMN_OBJECT) { | 763 | if (ti->tokens[*index].type != JSMN_OBJECT) { |
| 750 | PLIST_JSON_ERR("%s: token type != JSMN_OBJECT\n", __func__); | 764 | PLIST_JSON_ERR("%s: token type != JSMN_OBJECT\n", __func__); |
| 765 | ti->err = PLIST_ERR_PARSE; | ||
| 766 | return NULL; | ||
| 767 | } | ||
| 768 | if (depth > PLIST_MAX_NESTING_DEPTH) { | ||
| 769 | PLIST_JSON_ERR("%s: maximum nesting depth (%u) exceeded\n", __func__, (unsigned)PLIST_MAX_NESTING_DEPTH); | ||
| 770 | ti->err = PLIST_ERR_MAX_NESTING; | ||
| 751 | return NULL; | 771 | return NULL; |
| 752 | } | 772 | } |
| 753 | int num_tokens = ti->tokens[*index].size; | 773 | 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) | |||
| 755 | int j = (*index)+1; | 775 | int j = (*index)+1; |
| 756 | if (num_tokens % 2 != 0) { | 776 | if (num_tokens % 2 != 0) { |
| 757 | PLIST_JSON_ERR("%s: number of children must be even\n", __func__); | 777 | PLIST_JSON_ERR("%s: number of children must be even\n", __func__); |
| 778 | ti->err = PLIST_ERR_PARSE; | ||
| 758 | return NULL; | 779 | return NULL; |
| 759 | } | 780 | } |
| 760 | plist_t obj = plist_new_dict(); | 781 | plist_t obj = plist_new_dict(); |
| @@ -762,12 +783,14 @@ static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index) | |||
| 762 | if (j+1 >= ti->count) { | 783 | if (j+1 >= ti->count) { |
| 763 | PLIST_JSON_ERR("%s: token index out of valid range\n", __func__); | 784 | PLIST_JSON_ERR("%s: token index out of valid range\n", __func__); |
| 764 | plist_free(obj); | 785 | plist_free(obj); |
| 786 | ti->err = PLIST_ERR_PARSE; | ||
| 765 | return NULL; | 787 | return NULL; |
| 766 | } | 788 | } |
| 767 | if (ti->tokens[j].type == JSMN_STRING) { | 789 | if (ti->tokens[j].type == JSMN_STRING) { |
| 768 | char* key = unescape_string(js + ti->tokens[j].start, ti->tokens[j].end - ti->tokens[j].start, NULL); | 790 | char* key = unescape_string(js + ti->tokens[j].start, ti->tokens[j].end - ti->tokens[j].start, NULL); |
| 769 | if (!key) { | 791 | if (!key) { |
| 770 | plist_free(obj); | 792 | plist_free(obj); |
| 793 | ti->err = PLIST_ERR_PARSE; | ||
| 771 | return NULL; | 794 | return NULL; |
| 772 | } | 795 | } |
| 773 | plist_t val = NULL; | 796 | plist_t val = NULL; |
| @@ -775,10 +798,10 @@ static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index) | |||
| 775 | num++; | 798 | num++; |
| 776 | switch (ti->tokens[j].type) { | 799 | switch (ti->tokens[j].type) { |
| 777 | case JSMN_OBJECT: | 800 | case JSMN_OBJECT: |
| 778 | val = parse_object(js, ti, &j); | 801 | val = parse_object(js, ti, &j, depth+1); |
| 779 | break; | 802 | break; |
| 780 | case JSMN_ARRAY: | 803 | case JSMN_ARRAY: |
| 781 | val = parse_array(js, ti, &j); | 804 | val = parse_array(js, ti, &j, depth+1); |
| 782 | break; | 805 | break; |
| 783 | case JSMN_STRING: | 806 | case JSMN_STRING: |
| 784 | val = parse_string(js, ti, &j); | 807 | val = parse_string(js, ti, &j); |
| @@ -794,12 +817,14 @@ static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index) | |||
| 794 | } else { | 817 | } else { |
| 795 | free(key); | 818 | free(key); |
| 796 | plist_free(obj); | 819 | plist_free(obj); |
| 820 | ti->err = PLIST_ERR_PARSE; | ||
| 797 | return NULL; | 821 | return NULL; |
| 798 | } | 822 | } |
| 799 | free(key); | 823 | free(key); |
| 800 | } else { | 824 | } else { |
| 801 | PLIST_JSON_ERR("%s: keys must be of type STRING\n", __func__); | 825 | PLIST_JSON_ERR("%s: keys must be of type STRING\n", __func__); |
| 802 | plist_free(obj); | 826 | plist_free(obj); |
| 827 | ti->err = PLIST_ERR_PARSE; | ||
| 803 | return NULL; | 828 | return NULL; |
| 804 | } | 829 | } |
| 805 | } | 830 | } |
| @@ -859,7 +884,7 @@ plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist) | |||
| 859 | } | 884 | } |
| 860 | 885 | ||
| 861 | int startindex = 0; | 886 | int startindex = 0; |
| 862 | jsmntok_info_t ti = { tokens, parser.toknext }; | 887 | jsmntok_info_t ti = { tokens, parser.toknext, PLIST_ERR_SUCCESS }; |
| 863 | switch (tokens[startindex].type) { | 888 | switch (tokens[startindex].type) { |
| 864 | case JSMN_PRIMITIVE: | 889 | case JSMN_PRIMITIVE: |
| 865 | *plist = parse_primitive(json, &ti, &startindex); | 890 | *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) | |||
| 868 | *plist = parse_string(json, &ti, &startindex); | 893 | *plist = parse_string(json, &ti, &startindex); |
| 869 | break; | 894 | break; |
| 870 | case JSMN_ARRAY: | 895 | case JSMN_ARRAY: |
| 871 | *plist = parse_array(json, &ti, &startindex); | 896 | *plist = parse_array(json, &ti, &startindex, 0); |
| 872 | break; | 897 | break; |
| 873 | case JSMN_OBJECT: | 898 | case JSMN_OBJECT: |
| 874 | *plist = parse_object(json, &ti, &startindex); | 899 | *plist = parse_object(json, &ti, &startindex, 0); |
| 875 | break; | 900 | break; |
| 876 | default: | 901 | default: |
| 877 | break; | 902 | break; |
| 878 | } | 903 | } |
| 879 | free(tokens); | 904 | free(tokens); |
| 905 | if (!*plist) { | ||
| 906 | return (ti.err != PLIST_ERR_SUCCESS) ? ti.err : PLIST_ERR_PARSE; | ||
| 907 | } | ||
| 880 | return PLIST_ERR_SUCCESS; | 908 | return PLIST_ERR_SUCCESS; |
| 881 | } | 909 | } |
