implement a patch function that results in (almost) always calling the correct destructors default tip

Sun, 07 Sep 2025 17:08:26 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 07 Sep 2025 17:08:26 +0200
changeset 1365
e4135791687e
parent 1364
556e4e7608b1

implement a patch function that results in (almost) always calling the correct destructors

relates to #461

src/kv_list.c file | annotate | diff | comparison | revisions
--- a/src/kv_list.c	Sat Sep 06 11:57:17 2025 +0200
+++ b/src/kv_list.c	Sun Sep 07 17:08:26 2025 +0200
@@ -53,10 +53,79 @@
     struct cx_kv_list_map_s *map;
     const cx_list_class *list_methods;
     const cx_map_class *map_methods;
+    cx_destructor_func list_destr;
+    cx_destructor_func2 list_destr2;
+    void *list_destr_data;
+    cx_destructor_func map_destr;
+    cx_destructor_func2 map_destr2;
+    void *map_destr_data;
 };
 
+static void cx_kv_list_destructor_wrapper_list(void *list_ptr, void *elem) {
+    const cx_kv_list *kv_list = list_ptr;
+    // list destructor is already called with proper deref of the elem
+    if (kv_list->list_destr) {
+        kv_list->list_destr(elem);
+    }
+    if (kv_list->list_destr2) {
+        kv_list->list_destr2(kv_list->list_destr_data, elem);
+    }
+    if (kv_list->map_destr) {
+        kv_list->map_destr(elem);
+    }
+    if (kv_list->map_destr2) {
+        kv_list->map_destr2(kv_list->map_destr_data, elem);
+    }
+}
+
+static void cx_kv_list_destructor_wrapper_map(void *list_ptr, void *node_data) {
+    const cx_kv_list *kv_list = list_ptr;
+    // the elem called with a map destructor is a pointer to the node
+    // we need to deref the elem accordingly
+    void *elem = kv_list->list.list_base.collection.store_pointer ? *(void**)node_data : node_data;
+    if (kv_list->list_destr) {
+        kv_list->list_destr(elem);
+    }
+    if (kv_list->list_destr2) {
+        kv_list->list_destr2(kv_list->list_destr_data, elem);
+    }
+    if (kv_list->map_destr) {
+        kv_list->map_destr(elem);
+    }
+    if (kv_list->map_destr2) {
+        kv_list->map_destr2(kv_list->map_destr_data, elem);
+    }
+}
+
+static void cx_kv_list_update_destructors(cx_kv_list *list) {
+    // we copy the destructors to our custom fields and register
+    // an own destructor function which invokes all these
+    if (list->list.list_base.collection.simple_destructor != NULL) {
+        list->list_destr = list->list.list_base.collection.simple_destructor;
+        list->list.list_base.collection.simple_destructor = NULL;
+    }
+    if (list->list.list_base.collection.advanced_destructor != cx_kv_list_destructor_wrapper_list) {
+        list->list_destr2 = list->list.list_base.collection.advanced_destructor;
+        list->list_destr_data = list->list.list_base.collection.destructor_data;
+        list->list.list_base.collection.advanced_destructor = cx_kv_list_destructor_wrapper_list;
+        list->list.list_base.collection.destructor_data = list;
+    }
+    if (list->map->map_base.base.collection.simple_destructor != NULL) {
+        list->map_destr = list->map->map_base.base.collection.simple_destructor;
+        list->map->map_base.base.collection.simple_destructor = NULL;
+    }
+    if (list->map->map_base.base.collection.advanced_destructor != cx_kv_list_destructor_wrapper_map) {
+        list->map_destr2 = list->map->map_base.base.collection.advanced_destructor;
+        list->map_destr_data = list->map->map_base.base.collection.destructor_data;
+        list->map->map_base.base.collection.advanced_destructor = cx_kv_list_destructor_wrapper_map;
+        list->map->map_base.base.collection.destructor_data = list;
+    }
+}
+
 static void cx_kvl_deallocate(struct cx_list_s *list) {
     cx_kv_list *kv_list = (cx_kv_list*)list;
+    // patch the destructors
+    cx_kv_list_update_destructors(kv_list);
     // free the map first
     kv_list->map_methods->deallocate(&kv_list->map->map_base.base);
     kv_list->list_methods->deallocate(list);
@@ -110,20 +179,23 @@
         void *targetbuf
 ) {
     cx_kv_list *kv_list = (cx_kv_list*)list;
+    // patch the destructors
+    cx_kv_list_update_destructors(kv_list);
     // TODO: always use the target buffer to get the element first,
     //       then obtain the key, remove it from the map,
     //       and finally call any destructors manually
-    // TODO: map destructors are not called
     return kv_list->list_methods->remove(list, index, num, targetbuf);
 }
 
 static void cx_kvl_clear(struct cx_list_s *list) {
     cx_kv_list *kv_list = (cx_kv_list*)list;
-    // FIXME: this is probably totally bugged, because
-    //        a) the map may have destructors which are invoked triggering UAF
-    //        b) the destructors registered with the map are not called for all list elements
+    // patch the destructors
+    // but remove the wrapper from the map to avoid calling it twice
+    cx_kv_list_update_destructors(kv_list);
+    cxDefineAdvancedDestructor(&kv_list->map->map_base.base, NULL, NULL);
+    // clear the list
     kv_list->list_methods->clear(list);
-    // also clear all lookup entries
+    // then clear the map
     cxMapClear(&kv_list->map->map_base.base);
 }
 
@@ -150,7 +222,8 @@
         bool remove
 ) {
     cx_kv_list *kv_list = (cx_kv_list*)list;
-    // TODO: implement removal of the key in the map and calling the map destructors
+    cx_kv_list_update_destructors(kv_list);
+    // TODO: implement removal of the key in the map
     return kv_list->list_methods->find_remove(list, elem, remove);
 }
 
@@ -182,7 +255,8 @@
 
 static void cx_kvl_map_clear(struct cx_map_s *map) {
     cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list;
-    // TODO: iterate through the map elements and remove the key from the referenced list items
+    cx_kv_list_update_destructors(kv_list);
+    // TODO: iterate through the map elements, unlink them from the list, and free them
     kv_list->map_methods->clear(map);
 }
 
@@ -206,6 +280,7 @@
 
 int cx_kvl_map_remove(CxMap *map, CxHashKey key, void *targetbuf) {
     cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list;
+
     void *node_data;
     if (kv_list->map_methods->remove(map, key, &node_data)) {
         return 1;
@@ -217,20 +292,9 @@
 
     // check if the outside caller want's us to return or to destroy the element
     if (targetbuf == NULL) {
-        // destroy the element
-        // invoke all destructors from both the list and the map aspect
-        // (usually the user will only use one of the aspects anyway and if not, it's documented)
-        cx_invoke_destructor(&kv_list->list.list_base, node_data);
-        // note that we cannot use the macro, because it will use the wrong store_pointer information
-        CxMap *map_aspect = &kv_list->map->map_base.base;
-        if (map_aspect->collection.simple_destructor) {
-            map_aspect->collection.simple_destructor(
-                kv_list->list.list_base.collection.store_pointer ? *(void**)node_data : node_data);
-        }
-        if (map_aspect->collection.advanced_destructor) {
-            map_aspect->collection.advanced_destructor(map_aspect->collection.destructor_data,
-                kv_list->list.list_base.collection.store_pointer ? *(void**)node_data : node_data);
-        }
+        // patch the destructors and invoke them through the wrapper
+        cx_kv_list_update_destructors(kv_list);
+        cx_invoke_advanced_destructor(&kv_list->list.list_base, node_data);
     } else {
         // copy the element to the target buffer
         memcpy(targetbuf, node_data, kv_list->list.list_base.collection.elem_size);
@@ -316,6 +380,9 @@
     // reallocate the list to add memory for storing the metadata
     cx_kv_list *kv_list = cxRealloc(allocator, list, sizeof(struct cx_kv_list_s));
 
+    // zero the custom destructor information
+    memset((char*)kv_list + offsetof(cx_kv_list, list_destr), 0, sizeof(void*)*6);
+
     // if any of the reallocations failed, we bail out
     if (kv_map != NULL && kv_list != NULL) {
         map = (CxMap*) kv_map;

mercurial