diff options
| author | 2026-02-08 04:31:01 +0100 | |
|---|---|---|
| committer | 2026-02-08 04:31:01 +0100 | |
| commit | 9ef0d05265198ede1fd14271ab3f4812d34ebe2e (patch) | |
| tree | b8e90be6fd549ac0295eb3e348228d7c84464a68 | |
| parent | 714ef4f95652bc5dde2bc1a461cac8c3a89a61c9 (diff) | |
| download | libplist-9ef0d05265198ede1fd14271ab3f4812d34ebe2e.tar.gz libplist-9ef0d05265198ede1fd14271ab3f4812d34ebe2e.tar.bz2 | |
plist: Handle node_attach/node_insert failures
Update plist array and dict mutation helpers to check
return values from node_attach() and node_insert(). This
prevents cache corruption and allows new depth and cycle
checks to be enforced correctly.
| -rw-r--r-- | src/plist.c | 197 |
1 files changed, 154 insertions, 43 deletions
diff --git a/src/plist.c b/src/plist.c index a9199ee..31f754d 100644 --- a/src/plist.c +++ b/src/plist.c | |||
| @@ -415,8 +415,14 @@ void plist_free_data(plist_data_t data) | |||
| 415 | 415 | ||
| 416 | static int plist_free_node(node_t node) | 416 | static int plist_free_node(node_t node) |
| 417 | { | 417 | { |
| 418 | if (!node) return NODE_ERR_INVALID_ARG; | ||
| 418 | plist_data_t data = NULL; | 419 | plist_data_t data = NULL; |
| 419 | int node_index = node_detach(node->parent, node); | 420 | int node_index = -1; |
| 421 | |||
| 422 | if (node->parent) { | ||
| 423 | node_index = node_detach(node->parent, node); | ||
| 424 | } | ||
| 425 | |||
| 420 | data = plist_get_data(node); | 426 | data = plist_get_data(node); |
| 421 | plist_free_data(data); | 427 | plist_free_data(data); |
| 422 | node->data = NULL; | 428 | node->data = NULL; |
| @@ -627,8 +633,17 @@ static plist_t plist_copy_node(node_t node) | |||
| 627 | for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) { | 633 | for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) { |
| 628 | /* copy child node */ | 634 | /* copy child node */ |
| 629 | plist_t newch = plist_copy_node(ch); | 635 | plist_t newch = plist_copy_node(ch); |
| 636 | if (!newch) { | ||
| 637 | plist_free_node((node_t)newnode); | ||
| 638 | return NULL; | ||
| 639 | } | ||
| 630 | /* attach to new parent node */ | 640 | /* attach to new parent node */ |
| 631 | node_attach((node_t)newnode, (node_t)newch); | 641 | int r = node_attach((node_t)newnode, (node_t)newch); |
| 642 | if (r != NODE_ERR_SUCCESS) { | ||
| 643 | plist_free_node((node_t)newch); | ||
| 644 | plist_free_node((node_t)newnode); | ||
| 645 | return NULL; | ||
| 646 | } | ||
| 632 | /* if needed, add child node to lookup table of parent node */ | 647 | /* if needed, add child node to lookup table of parent node */ |
| 633 | switch (node_type) { | 648 | switch (node_type) { |
| 634 | case PLIST_ARRAY: | 649 | case PLIST_ARRAY: |
| @@ -695,18 +710,50 @@ static void _plist_array_post_insert(plist_t node, plist_t item, long n) | |||
| 695 | if (pa) { | 710 | if (pa) { |
| 696 | /* store pointer to item in array */ | 711 | /* store pointer to item in array */ |
| 697 | ptr_array_insert(pa, item, n); | 712 | ptr_array_insert(pa, item, n); |
| 698 | } else { | 713 | return; |
| 699 | if (((node_t)node)->count > 100) { | 714 | } |
| 700 | /* make new lookup array */ | 715 | |
| 701 | pa = ptr_array_new(128); | 716 | if (((node_t)node)->count > 100) { |
| 702 | plist_t current = NULL; | 717 | /* make new lookup array */ |
| 703 | for (current = (plist_t)node_first_child((node_t)node); | 718 | pa = ptr_array_new(128); |
| 704 | pa && current; | 719 | plist_t current = NULL; |
| 705 | current = (plist_t)node_next_sibling((node_t)current)) | 720 | for (current = (plist_t)node_first_child((node_t)node); |
| 706 | { | 721 | pa && current; |
| 707 | ptr_array_add(pa, current); | 722 | current = (plist_t)node_next_sibling((node_t)current)) |
| 708 | } | 723 | { |
| 709 | ((plist_data_t)((node_t)node)->data)->hashtable = pa; | 724 | ptr_array_add(pa, current); |
| 725 | } | ||
| 726 | ((plist_data_t)((node_t)node)->data)->hashtable = pa; | ||
| 727 | } | ||
| 728 | } | ||
| 729 | |||
| 730 | static void _plist_array_post_set(plist_t node, plist_t item, long n) | ||
| 731 | { | ||
| 732 | ptrarray_t *pa = (ptrarray_t*)((plist_data_t)((node_t)node)->data)->hashtable; | ||
| 733 | |||
| 734 | if (pa) { | ||
| 735 | if (n < 0 || n >= pa->len) { | ||
| 736 | PLIST_ERR("%s: cache index out of range (n=%ld len=%ld)\n", __func__, n, pa->len); | ||
| 737 | return; | ||
| 738 | } | ||
| 739 | ptr_array_set(pa, item, n); | ||
| 740 | return; | ||
| 741 | } | ||
| 742 | |||
| 743 | if (((node_t)node)->count > 100) { | ||
| 744 | pa = ptr_array_new(128); | ||
| 745 | plist_t current = NULL; | ||
| 746 | for (current = (plist_t)node_first_child((node_t)node); | ||
| 747 | pa && current; | ||
| 748 | current = (plist_t)node_next_sibling((node_t)current)) | ||
| 749 | { | ||
| 750 | ptr_array_add(pa, current); | ||
| 751 | } | ||
| 752 | ((plist_data_t)((node_t)node)->data)->hashtable = pa; | ||
| 753 | |||
| 754 | // Now that it exists (and is filled), apply the set (will no-op if out of range) | ||
| 755 | if (pa) { | ||
| 756 | ptr_array_set(pa, item, n); | ||
| 710 | } | 757 | } |
| 711 | } | 758 | } |
| 712 | } | 759 | } |
| @@ -724,19 +771,28 @@ void plist_array_set_item(plist_t node, plist_t item, uint32_t n) | |||
| 724 | return; | 771 | return; |
| 725 | } | 772 | } |
| 726 | plist_t old_item = plist_array_get_item(node, n); | 773 | plist_t old_item = plist_array_get_item(node, n); |
| 727 | if (old_item) | 774 | if (!old_item) return; |
| 728 | { | 775 | |
| 729 | int idx = plist_free_node((node_t)old_item); | 776 | int idx = node_detach((node_t)node, (node_t)old_item); |
| 730 | assert(idx >= 0); | 777 | if (idx < 0) { |
| 731 | if (idx < 0) { | 778 | PLIST_ERR("%s: Failed to detach old item (err=%d)\n", __func__, idx); |
| 732 | return; | 779 | return; |
| 733 | } | 780 | } |
| 734 | node_insert((node_t)node, idx, (node_t)item); | 781 | |
| 735 | ptrarray_t* pa = (ptrarray_t*)((plist_data_t)((node_t)node)->data)->hashtable; | 782 | int r = node_insert((node_t)node, (unsigned)idx, (node_t)item); |
| 736 | if (pa) { | 783 | if (r != NODE_ERR_SUCCESS) { |
| 737 | ptr_array_set(pa, item, idx); | 784 | int rb = node_insert((node_t)node, (unsigned)idx, (node_t)old_item); |
| 785 | if (rb == NODE_ERR_SUCCESS) { | ||
| 786 | _plist_array_post_set(node, old_item, idx); // restore cache correctly | ||
| 787 | PLIST_ERR("%s: failed to insert replacement (idx=%d err=%d); rollback succeeded\n", __func__, idx, r); | ||
| 788 | } else { | ||
| 789 | PLIST_ERR("%s: insert failed (err=%d) and rollback failed (err=%d); array now missing element at idx=%d\n", __func__, r, rb, idx); | ||
| 738 | } | 790 | } |
| 791 | return; | ||
| 739 | } | 792 | } |
| 793 | |||
| 794 | _plist_array_post_set(node, item, idx); // update cache | ||
| 795 | plist_free_node((node_t)old_item); | ||
| 740 | } | 796 | } |
| 741 | 797 | ||
| 742 | void plist_array_append_item(plist_t node, plist_t item) | 798 | void plist_array_append_item(plist_t node, plist_t item) |
| @@ -751,7 +807,12 @@ void plist_array_append_item(plist_t node, plist_t item) | |||
| 751 | PLIST_ERR("%s: item already has a parent; use plist_copy() or detach first\n", __func__); | 807 | PLIST_ERR("%s: item already has a parent; use plist_copy() or detach first\n", __func__); |
| 752 | return; | 808 | return; |
| 753 | } | 809 | } |
| 754 | node_attach((node_t)node, (node_t)item); | 810 | |
| 811 | int r = node_attach((node_t)node, (node_t)item); | ||
| 812 | if (r != NODE_ERR_SUCCESS) { | ||
| 813 | PLIST_ERR("%s: failed to append item (err=%d)\n", __func__, r); | ||
| 814 | return; | ||
| 815 | } | ||
| 755 | _plist_array_post_insert(node, item, -1); | 816 | _plist_array_post_insert(node, item, -1); |
| 756 | } | 817 | } |
| 757 | 818 | ||
| @@ -767,7 +828,12 @@ void plist_array_insert_item(plist_t node, plist_t item, uint32_t n) | |||
| 767 | PLIST_ERR("%s: item already has a parent; use plist_copy() or detach first\n", __func__); | 828 | PLIST_ERR("%s: item already has a parent; use plist_copy() or detach first\n", __func__); |
| 768 | return; | 829 | return; |
| 769 | } | 830 | } |
| 770 | node_insert((node_t)node, n, (node_t)item); | 831 | |
| 832 | int r = node_insert((node_t)node, n, (node_t)item); | ||
| 833 | if (r != NODE_ERR_SUCCESS) { | ||
| 834 | PLIST_ERR("%s: Failed to insert item at index %u (err=%d)\n", __func__, n, r); | ||
| 835 | return; | ||
| 836 | } | ||
| 771 | _plist_array_post_insert(node, item, (long)n); | 837 | _plist_array_post_insert(node, item, (long)n); |
| 772 | } | 838 | } |
| 773 | 839 | ||
| @@ -950,31 +1016,76 @@ void plist_dict_set_item(plist_t node, const char* key, plist_t item) | |||
| 950 | PLIST_ERR("%s: item already has a parent\n", __func__); | 1016 | PLIST_ERR("%s: item already has a parent\n", __func__); |
| 951 | return; | 1017 | return; |
| 952 | } | 1018 | } |
| 1019 | |||
| 1020 | hashtable_t *ht = (hashtable_t*)((plist_data_t)((node_t)node)->data)->hashtable; | ||
| 1021 | |||
| 953 | plist_t old_item = plist_dict_get_item(node, key); | 1022 | plist_t old_item = plist_dict_get_item(node, key); |
| 954 | plist_t key_node = NULL; | 1023 | plist_t key_node = NULL; |
| 955 | if (old_item) { | 1024 | |
| 956 | int idx = plist_free_node((node_t)old_item); | 1025 | if (old_item) { |
| 957 | assert(idx >= 0); | 1026 | // --- REPLACE EXISTING VALUE --- |
| 1027 | node_t old_val = (node_t)old_item; | ||
| 1028 | node_t old_key = node_prev_sibling(old_val); | ||
| 1029 | if (!old_key) { | ||
| 1030 | PLIST_ERR("%s: corrupt dict (value without key)\n", __func__); | ||
| 1031 | return; | ||
| 1032 | } | ||
| 1033 | assert(PLIST_IS_KEY((plist_t)old_key)); | ||
| 1034 | |||
| 1035 | // detach old value (do NOT free yet) | ||
| 1036 | int idx = node_detach((node_t)node, old_val); | ||
| 958 | if (idx < 0) { | 1037 | if (idx < 0) { |
| 1038 | PLIST_ERR("%s: failed to detach old value (err=%d)\n", __func__, idx); | ||
| 1039 | return; | ||
| 1040 | } | ||
| 1041 | |||
| 1042 | // insert new value at same position | ||
| 1043 | int r = node_insert((node_t)node, (unsigned)idx, (node_t)item); | ||
| 1044 | if (r != NODE_ERR_SUCCESS) { | ||
| 1045 | // rollback: reinsert old value | ||
| 1046 | int rb = node_insert((node_t)node, (unsigned)idx, old_val); | ||
| 1047 | if (rb == NODE_ERR_SUCCESS && ht) { | ||
| 1048 | hash_table_insert(ht, ((node_t)old_key)->data, old_item); | ||
| 1049 | } | ||
| 1050 | PLIST_ERR("%s: failed to replace dict value (err=%d)\n", __func__, r); | ||
| 959 | return; | 1051 | return; |
| 960 | } | 1052 | } |
| 961 | node_insert((node_t)node, idx, (node_t)item); | 1053 | key_node = old_key; |
| 962 | key_node = node_prev_sibling((node_t)item); | 1054 | |
| 1055 | // update hash table | ||
| 1056 | if (ht) { | ||
| 1057 | hash_table_insert(ht, (plist_data_t)((node_t)key_node)->data, item); | ||
| 1058 | } | ||
| 1059 | |||
| 1060 | // now it’s safe to free old value | ||
| 1061 | plist_free_node(old_val); | ||
| 963 | } else { | 1062 | } else { |
| 1063 | // --- INSERT NEW KEY/VALUE PAIR --- | ||
| 964 | key_node = plist_new_key(key); | 1064 | key_node = plist_new_key(key); |
| 965 | node_attach((node_t)node, (node_t)key_node); | 1065 | if (!key_node) return; |
| 966 | node_attach((node_t)node, (node_t)item); | ||
| 967 | } | ||
| 968 | 1066 | ||
| 969 | hashtable_t *ht = (hashtable_t*)((plist_data_t)((node_t)node)->data)->hashtable; | 1067 | int r = node_attach((node_t)node, (node_t)key_node); |
| 970 | if (ht) { | 1068 | if (r != NODE_ERR_SUCCESS) { |
| 971 | /* store pointer to item in hash table */ | 1069 | plist_free_node((node_t)key_node); |
| 972 | hash_table_insert(ht, (plist_data_t)((node_t)key_node)->data, item); | 1070 | PLIST_ERR("%s: failed to attach dict key (err=%d)\n", __func__, r); |
| 973 | } else { | 1071 | return; |
| 974 | if (((node_t)node)->count > 500) { | 1072 | } |
| 975 | /* make new hash table */ | 1073 | r = node_attach((node_t)node, (node_t)item); |
| 1074 | if (r != NODE_ERR_SUCCESS) { | ||
| 1075 | // rollback key insertion | ||
| 1076 | node_detach((node_t)node, (node_t)key_node); | ||
| 1077 | plist_free_node((node_t)key_node); | ||
| 1078 | PLIST_ERR("%s: failed to attach dict value (err=%d)\n", __func__, r); | ||
| 1079 | return; | ||
| 1080 | } | ||
| 1081 | |||
| 1082 | if (ht) { | ||
| 1083 | // store pointer to item in hash table | ||
| 1084 | hash_table_insert(ht, (plist_data_t)((node_t)key_node)->data, item); | ||
| 1085 | } else if (((node_t)node)->count > 500) { | ||
| 1086 | // make new hash table | ||
| 976 | ht = hash_table_new(dict_key_hash, dict_key_compare, NULL); | 1087 | ht = hash_table_new(dict_key_hash, dict_key_compare, NULL); |
| 977 | /* calculate the hashes for all entries we have so far */ | 1088 | // calculate the hashes for all entries we have so far |
| 978 | plist_t current = NULL; | 1089 | plist_t current = NULL; |
| 979 | for (current = (plist_t)node_first_child((node_t)node); | 1090 | for (current = (plist_t)node_first_child((node_t)node); |
| 980 | ht && current; | 1091 | ht && current; |
