summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Nikias Bassen2026-02-08 04:31:01 +0100
committerGravatar Nikias Bassen2026-02-08 04:31:01 +0100
commit9ef0d05265198ede1fd14271ab3f4812d34ebe2e (patch)
treeb8e90be6fd549ac0295eb3e348228d7c84464a68
parent714ef4f95652bc5dde2bc1a461cac8c3a89a61c9 (diff)
downloadlibplist-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.c197
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
416static int plist_free_node(node_t node) 416static 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
730static 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
742void plist_array_append_item(plist_t node, plist_t item) 798void 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;