map: fix free_list corruption when re-using removed ids

Re-using an id after removing it is a bug in the caller but there are
two cases where we corrupt the free list without warning:

Removing an object twice:

   id = pw_map_insert_new(object);
   pw_map_remove(map, id);
   pw_map_remove(map, id);

And inserting an element at an index previously removed:

   id = pw_map_insert_new(object);
   pw_map_remove(map, id);
   pw_map_insert_at(map, id, new_object);

The latter is arguably valid code, or at least it'll look like it's
valid code.

For both cases, check if the id to remove/insert at is a free item and
handle that accordingly.
This commit is contained in:
Peter Hutterer 2021-09-30 11:58:49 +10:00
parent 5b9447c2a4
commit 626d30e4bd
2 changed files with 109 additions and 2 deletions

View file

@ -142,8 +142,20 @@ static inline int pw_map_insert_at(struct pw_map *map, uint32_t id, void *data)
item = (union pw_map_item *) pw_array_add(&map->items, sizeof(union pw_map_item));
if (item == NULL)
return -errno;
}
else {
} else {
if (pw_map_id_is_free(map, id)) {
uint32_t *current = &map->free_list;
while (*current != SPA_ID_INVALID) {
uint32_t current_id = (*current) >> 1;
uint32_t *next = &pw_map_get_item(map, current_id)->next;
if (current_id == id) {
*current = *next;
break;
}
current = next;
}
}
item = pw_map_get_item(map, id);
}
item->data = data;
@ -156,6 +168,9 @@ static inline int pw_map_insert_at(struct pw_map *map, uint32_t id, void *data)
*/
static inline void pw_map_remove(struct pw_map *map, uint32_t id)
{
if (pw_map_id_is_free(map, id))
return;
pw_map_get_item(map, id)->next = map->free_list;
map->free_list = (id << 1) | 1;
}