Sat, 13 Dec 2025 16:27:16 +0100
fix critical UAF because kv-list stored pointers to the wrong key data
| CHANGELOG | file | annotate | diff | comparison | revisions | |
| docs/Writerside/topics/about.md | file | annotate | diff | comparison | revisions | |
| docs/Writerside/topics/map.h.md | file | annotate | diff | comparison | revisions | |
| src/cx/map.h | file | annotate | diff | comparison | revisions | |
| src/hash_map.c | file | annotate | diff | comparison | revisions | |
| src/kv_list.c | file | annotate | diff | comparison | revisions | |
| src/map.c | file | annotate | diff | comparison | revisions |
--- a/CHANGELOG Sat Dec 13 15:16:25 2025 +0100 +++ b/CHANGELOG Sat Dec 13 16:27:16 2025 +0100 @@ -21,6 +21,7 @@ * fixes cxJsonWrite() incorrectly returning non-zero when strings needed to be escaped * fixes cxJsonNext() incorrectly returning CX_JSON_INCOMPLETE_DATA when the input ends with trailing spaces * fixes critical memory leak when using cxMapFree() on a kv-list that is using destructors + * fixes critical use-after-free with keys stored in k/v-lists * fixes that overwriting items with cxMapPut() in a kv-list did not work * fixes that cxReallocate(), cxReallocateArray(), cx_reallocate(), and cx_reallocatearray() were not returning zero after freeing the memory when passed a size of zero
--- a/docs/Writerside/topics/about.md Sat Dec 13 15:16:25 2025 +0100 +++ b/docs/Writerside/topics/about.md Sat Dec 13 16:27:16 2025 +0100 @@ -48,6 +48,7 @@ * fixes cxJsonWrite() incorrectly returning non-zero when strings needed to be escaped * fixes cxJsonNext() incorrectly returning CX_JSON_INCOMPLETE_DATA when the input ends with trailing spaces * fixes critical memory leak when using cxMapFree() on a kv-list that is using destructors +* fixes critical use-after-free with keys stored in k/v-lists * fixes that overwriting items with cxMapPut() in a kv-list did not work * fixes that cxReallocate(), cxReallocateArray(), cx_reallocate(), and cx_reallocatearray() were not returning zero after freeing the memory when passed a size of zero
--- a/docs/Writerside/topics/map.h.md Sat Dec 13 15:16:25 2025 +0100 +++ b/docs/Writerside/topics/map.h.md Sat Dec 13 16:27:16 2025 +0100 @@ -469,14 +469,14 @@ The required behavior for the implementations is described in the following table. You can always look at the source code of the UCX hash map to get inspiration. -| Function | Description | -|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `clear` | Invoke [destructor functions](collection.h.md#destructor-functions) on all elements and remove them from the map. | -| `deallocate` | Invoke destructor functions on all elements and deallocate the entire map memory. | -| `put` | Store an element in the map. If an element is already stored, invoke the destructor functions on that element and replace it with the new element. When the value pointer is `NULL`, only allocate memory without copying an existing value. Return a pointer to the allocated memory, or `NULL` when allocation fails. | -| `get` | Look up the specified key and return the associated value (or `NULL` if the key was not found). | -| `remove` | Remove an element from the map. If a target buffer is specified, copy the elements to that buffer. Otherwise, invoke the destructor functions for the element. If the key was not found in the map, return non-zero. | -| `iterator` | Return an iterator over the pairs, the keys, or the values, depending on the iterator type passed with the last argument. | +| Function | Description | +|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `clear` | Invoke [destructor functions](collection.h.md#destructor-functions) on all elements and remove them from the map. | +| `deallocate` | Invoke destructor functions on all elements and deallocate the entire map memory. | +| `put` | Store an element in the map. If an element is already stored, invoke the destructor functions on that element and replace it with the new element. When the value pointer is `NULL`, only allocate memory without copying an existing value. Return `CxMapEntry` where the `key` is `NULL` when allocation fails. | +| `get` | Look up the specified key and return the associated value (or `NULL` if the key was not found). | +| `remove` | Remove an element from the map. If a target buffer is specified, copy the elements to that buffer. Otherwise, invoke the destructor functions for the element. If the key was not found in the map, return non-zero. | +| `iterator` | Return an iterator over the pairs, the keys, or the values, depending on the iterator type passed with the last argument. | > In contrast to the list interface, there is no `cx_map_init()` function which automatically > configures a wrapping mechanism when `CX_STORE_POINTERS` is used.
--- a/src/cx/map.h Sat Dec 13 15:16:25 2025 +0100 +++ b/src/cx/map.h Sat Dec 13 16:27:16 2025 +0100 @@ -183,9 +183,9 @@ * Add or overwrite an element. * If the @p value is @c NULL, the implementation * shall only allocate memory instead of adding an existing value to the map. - * Returns a pointer to the allocated memory or @c NULL if allocation fails. + * Returns a map entry where the pointer to the key is @c NULL if allocation fails. */ - void *(*put)(CxMap *map, CxHashKey key, void *value); + CxMapEntry (*put)(CxMap *map, CxHashKey key, void *value); /** * Returns an element.
--- a/src/hash_map.c Sat Dec 13 15:16:25 2025 +0100 +++ b/src/hash_map.c Sat Dec 13 16:27:16 2025 +0100 @@ -78,7 +78,7 @@ cxFree(map->collection.allocator, map); } -static void *cx_hash_map_put( +static CxMapEntry cx_hash_map_put( CxMap *map, CxHashKey key, void *value @@ -117,7 +117,7 @@ allocator, sizeof(struct cx_hash_map_element_s) + map->collection.elem_size ); - if (e == NULL) return NULL; // LCOV_EXCL_LINE + if (e == NULL) return (CxMapEntry){NULL, NULL}; // LCOV_EXCL_LINE // write the value if (value == NULL) { @@ -132,7 +132,7 @@ void *kd = cxMalloc(allocator, key.len); if (kd == NULL) { // LCOV_EXCL_START cxFree(allocator, e); - return NULL; + return (CxMapEntry){NULL, NULL}; } // LCOV_EXCL_STOP memcpy(kd, key.data, key.len); e->key.data = kd; @@ -152,8 +152,8 @@ map->collection.size++; } - // return pointer to the element - return elm->data; + // return the entry + return (CxMapEntry){&elm->key, elm->data}; } static void cx_hash_map_unlink(
--- a/src/kv_list.c Sat Dec 13 15:16:25 2025 +0100 +++ b/src/kv_list.c Sat Dec 13 16:27:16 2025 +0100 @@ -348,7 +348,7 @@ return 0; } -static void *cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) { +static CxMapEntry cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) { cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; // if the hash has not yet been computed, do it now if (key.hash == 0) { @@ -359,8 +359,8 @@ cx_kvl_map_remove(map, key, NULL); // now reserve new memory in the map - void **map_data = kv_list->map_methods->put(map, key, NULL); - if (map_data == NULL) return NULL; // LCOV_EXCL_LINE + CxMapEntry map_entry = kv_list->map_methods->put(map, key, NULL); + if (map_entry.key == NULL) return (CxMapEntry){NULL, NULL}; // LCOV_EXCL_LINE // insert the data into the list (which most likely destroys the sorted property) kv_list->list.base.collection.sorted = false; @@ -369,20 +369,19 @@ kv_list->list.base.collection.store_pointer ? &value : value); if (node_data == NULL) { // LCOV_EXCL_START // non-destructively remove the key again - kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); - return NULL; + void *dummy; + kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &dummy); + return (CxMapEntry){NULL, NULL}; } // LCOV_EXCL_STOP // write the node pointer to the map entry - *map_data = node_data; + *(void**)map_entry.value = node_data; // copy the key to the node data CxHashKey *key_ptr = cx_kv_list_loc_key(kv_list, node_data); - *key_ptr = key; + *key_ptr = *map_entry.key; - // we must return node_data here and not map_data, - // because the node_data is the actual element of this collection - return node_data; + return map_entry; } static void *cx_kvl_iter_current_entry(const void *it) { @@ -649,14 +648,14 @@ return 1; } - // add the key to the map; - if (NULL == kv_list->map_methods->put(&kv_list->map->map_base.base, key, node_data)) { - return 1; // LCOV_EXCL_LINE - } + // add the key to the map + const CxMapEntry entry = kv_list->map_methods->put( + &kv_list->map->map_base.base, key, node_data); + if (entry.key == NULL) return 1; // LCOV_EXCL_LINE // write the key to the list's node CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); - *loc_key = key; + *loc_key = *entry.key; return 0; } @@ -698,22 +697,23 @@ cx_kv_list *kv_list = (cx_kv_list*)list; // reserve memory in the map - void **map_data = kv_list->map_methods->put(&kv_list->map->map_base.base, key, NULL); - if (map_data == NULL) return 1; // LCOV_EXCL_LINE + CxMapEntry map_entry = kv_list->map_methods->put(&kv_list->map->map_base.base, key, NULL); + if (map_entry.key == NULL) return 1; // LCOV_EXCL_LINE // insert the node void *node_data = kv_list->list_methods->insert_element(&kv_list->list.base, index, kv_list->list.base.collection.store_pointer ? &value : value); if (node_data == NULL) { // LCOV_EXCL_START // non-destructively remove the key again - kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); + void *dummy; + kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &dummy); return 1; } // LCOV_EXCL_STOP - *map_data = node_data; + *(void**)map_entry.value = node_data; // write the key to the node CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); - *loc_key = key; + *loc_key = *map_entry.key; return 0; }
--- a/src/map.c Sat Dec 13 15:16:25 2025 +0100 +++ b/src/map.c Sat Dec 13 16:27:16 2025 +0100 @@ -110,11 +110,11 @@ } int cx_map_put(CxMap *map, CxHashKey key, void *value) { - return map->cl->put(map, key, value) == NULL; + return map->cl->put(map, key, value).key == NULL; } void *cx_map_emplace(CxMap *map, CxHashKey key) { - return map->cl->put(map, key, NULL); + return map->cl->put(map, key, NULL).value; } void *cx_map_get(const CxMap *map, CxHashKey key) {