summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Nikias Bassen2026-01-20 09:52:02 +0100
committerGravatar Nikias Bassen2026-01-20 09:55:40 +0100
commitcff6a14ba4d0964c4fb4843aad84db12b4df2854 (patch)
tree7ddb25e2b98088f0fcfea345eddebe9366f39572
parent001a59eef3b2a981f28af74ea82e1fc06b0c4275 (diff)
downloadlibplist-cff6a14ba4d0964c4fb4843aad84db12b4df2854.tar.gz
libplist-cff6a14ba4d0964c4fb4843aad84db12b4df2854.tar.bz2
plist: Reject insertion of plist nodes that already have a parent
Credit to @LkkkLxy for reporting (#276). libplist nodes are owned by exactly one container. Inserting the same plist_t into multiple dicts or arrays corrupts the tree structure and leads to use-after-free crashes during traversal or plist_free(). Add explicit parent checks to dict and array insertion APIs to reject nodes that already belong to another container. In debug builds, this fails loudly via assert() and optional diagnostics; in release builds, the operation safely no-ops. Callers that need to reuse values must create a copy using plist_copy() or explicitly detach the node before reinserting it.
-rw-r--r--src/plist.c131
1 files changed, 74 insertions, 57 deletions
diff --git a/src/plist.c b/src/plist.c
index 17b8419..9a488bb 100644
--- a/src/plist.c
+++ b/src/plist.c
@@ -698,50 +698,62 @@ static void _plist_array_post_insert(plist_t node, plist_t item, long n)
698 698
699void plist_array_set_item(plist_t node, plist_t item, uint32_t n) 699void plist_array_set_item(plist_t node, plist_t item, uint32_t n)
700{ 700{
701 if (!item) { 701 if (!PLIST_IS_ARRAY(node) || !item || n >= INT_MAX) {
702 PLIST_ERR("invalid argument passed to %s (node=%p, item=%p, n=%u)\n", __func__, node, item, n);
702 return; 703 return;
703 } 704 }
704 if (node && PLIST_ARRAY == plist_get_node_type(node) && n < INT_MAX) 705 node_t it = (node_t)item;
706 if (it->parent != NULL) {
707 assert(it->parent == NULL && "item already has a parent; use plist_copy() or detach first");
708 PLIST_ERR("%s: item already has a parent; use plist_copy() or detach first\n", __func__);
709 return;
710 }
711 plist_t old_item = plist_array_get_item(node, n);
712 if (old_item)
705 { 713 {
706 plist_t old_item = plist_array_get_item(node, n); 714 int idx = plist_free_node((node_t)old_item);
707 if (old_item) 715 assert(idx >= 0);
708 { 716 if (idx < 0) {
709 int idx = plist_free_node((node_t)old_item); 717 return;
710 assert(idx >= 0); 718 }
711 if (idx < 0) { 719 node_insert((node_t)node, idx, (node_t)item);
712 return; 720 ptrarray_t* pa = (ptrarray_t*)((plist_data_t)((node_t)node)->data)->hashtable;
713 } 721 if (pa) {
714 node_insert((node_t)node, idx, (node_t)item); 722 ptr_array_set(pa, item, idx);
715 ptrarray_t* pa = (ptrarray_t*)((plist_data_t)((node_t)node)->data)->hashtable;
716 if (pa) {
717 ptr_array_set(pa, item, idx);
718 }
719 } 723 }
720 } 724 }
721} 725}
722 726
723void plist_array_append_item(plist_t node, plist_t item) 727void plist_array_append_item(plist_t node, plist_t item)
724{ 728{
725 if (!item) { 729 if (!PLIST_IS_ARRAY(node) || !item) {
730 PLIST_ERR("invalid argument passed to %s (node=%p, item=%p)\n", __func__, node, item);
726 return; 731 return;
727 } 732 }
728 if (node && PLIST_ARRAY == plist_get_node_type(node)) 733 node_t it = (node_t)item;
729 { 734 if (it->parent != NULL) {
730 node_attach((node_t)node, (node_t)item); 735 assert(it->parent == NULL && "item already has a parent; use plist_copy() or detach first");
731 _plist_array_post_insert(node, item, -1); 736 PLIST_ERR("%s: item already has a parent; use plist_copy() or detach first\n", __func__);
737 return;
732 } 738 }
739 node_attach((node_t)node, (node_t)item);
740 _plist_array_post_insert(node, item, -1);
733} 741}
734 742
735void plist_array_insert_item(plist_t node, plist_t item, uint32_t n) 743void plist_array_insert_item(plist_t node, plist_t item, uint32_t n)
736{ 744{
737 if (!item) { 745 if (!PLIST_IS_ARRAY(node) || !item || n >= INT_MAX) {
746 PLIST_ERR("invalid argument passed to %s (node=%p, item=%p, n=%u)\n", __func__, node, item, n);
738 return; 747 return;
739 } 748 }
740 if (node && PLIST_ARRAY == plist_get_node_type(node) && n < INT_MAX) 749 node_t it = (node_t)item;
741 { 750 if (it->parent != NULL) {
742 node_insert((node_t)node, n, (node_t)item); 751 assert(it->parent == NULL && "item already has a parent; use plist_copy() or detach first");
743 _plist_array_post_insert(node, item, (long)n); 752 PLIST_ERR("%s: item already has a parent; use plist_copy() or detach first\n", __func__);
753 return;
744 } 754 }
755 node_insert((node_t)node, n, (node_t)item);
756 _plist_array_post_insert(node, item, (long)n);
745} 757}
746 758
747void plist_array_remove_item(plist_t node, uint32_t n) 759void plist_array_remove_item(plist_t node, uint32_t n)
@@ -905,44 +917,49 @@ plist_t plist_dict_get_item(plist_t node, const char* key)
905 917
906void plist_dict_set_item(plist_t node, const char* key, plist_t item) 918void plist_dict_set_item(plist_t node, const char* key, plist_t item)
907{ 919{
908 if (!item) { 920 if (!PLIST_IS_DICT(node) || !key || !item) {
921 PLIST_ERR("invalid argument passed to %s (node=%p, key=%p, item=%p)\n", __func__, node, key, item);
909 return; 922 return;
910 } 923 }
911 if (node && PLIST_DICT == plist_get_node_type(node)) { 924 node_t it = (node_t)item;
912 plist_t old_item = plist_dict_get_item(node, key); 925 if (it->parent != NULL) {
913 plist_t key_node = NULL; 926 assert(it->parent == NULL && "item already has a parent");
927 PLIST_ERR("%s: item already has a parent\n", __func__);
928 return;
929 }
930 plist_t old_item = plist_dict_get_item(node, key);
931 plist_t key_node = NULL;
914 if (old_item) { 932 if (old_item) {
915 int idx = plist_free_node((node_t)old_item); 933 int idx = plist_free_node((node_t)old_item);
916 assert(idx >= 0); 934 assert(idx >= 0);
917 if (idx < 0) { 935 if (idx < 0) {
918 return; 936 return;
919 }
920 node_insert((node_t)node, idx, (node_t)item);
921 key_node = node_prev_sibling((node_t)item);
922 } else {
923 key_node = plist_new_key(key);
924 node_attach((node_t)node, (node_t)key_node);
925 node_attach((node_t)node, (node_t)item);
926 } 937 }
938 node_insert((node_t)node, idx, (node_t)item);
939 key_node = node_prev_sibling((node_t)item);
940 } else {
941 key_node = plist_new_key(key);
942 node_attach((node_t)node, (node_t)key_node);
943 node_attach((node_t)node, (node_t)item);
944 }
927 945
928 hashtable_t *ht = (hashtable_t*)((plist_data_t)((node_t)node)->data)->hashtable; 946 hashtable_t *ht = (hashtable_t*)((plist_data_t)((node_t)node)->data)->hashtable;
929 if (ht) { 947 if (ht) {
930 /* store pointer to item in hash table */ 948 /* store pointer to item in hash table */
931 hash_table_insert(ht, (plist_data_t)((node_t)key_node)->data, item); 949 hash_table_insert(ht, (plist_data_t)((node_t)key_node)->data, item);
932 } else { 950 } else {
933 if (((node_t)node)->count > 500) { 951 if (((node_t)node)->count > 500) {
934 /* make new hash table */ 952 /* make new hash table */
935 ht = hash_table_new(dict_key_hash, dict_key_compare, NULL); 953 ht = hash_table_new(dict_key_hash, dict_key_compare, NULL);
936 /* calculate the hashes for all entries we have so far */ 954 /* calculate the hashes for all entries we have so far */
937 plist_t current = NULL; 955 plist_t current = NULL;
938 for (current = (plist_t)node_first_child((node_t)node); 956 for (current = (plist_t)node_first_child((node_t)node);
939 ht && current; 957 ht && current;
940 current = (plist_t)node_next_sibling(node_next_sibling((node_t)current))) 958 current = (plist_t)node_next_sibling(node_next_sibling((node_t)current)))
941 { 959 {
942 hash_table_insert(ht, ((node_t)current)->data, node_next_sibling((node_t)current)); 960 hash_table_insert(ht, ((node_t)current)->data, node_next_sibling((node_t)current));
943 }
944 ((plist_data_t)((node_t)node)->data)->hashtable = ht;
945 } 961 }
962 ((plist_data_t)((node_t)node)->data)->hashtable = ht;
946 } 963 }
947 } 964 }
948} 965}