libretro-common: harden linked_list allocation, drop string_list growth memset

linked_list: linked_list_add(), linked_list_insert() and
linked_list_iterator() dereferenced malloc() results without a NULL
check.  Bail out on OOM instead (list unchanged / NULL iterator),
matching the treatment generic_queue already received.

string_list: string_list_capacity() zeroed the entire newly grown half
of the element array on every doubling.  Slots in [size, cap) are never
read - string_list_free() walks [0, size) and the append paths fully
initialise a slot when it becomes live - so the memset was pure
bandwidth (hundreds of thousands of elements cleared to add one entry
on large lists).  Remove it and have string_list_append()/_append_n()
set userdata = NULL explicitly so string_list_free() cannot free
garbage.  string_list_clone() already calloc()s its own array and every
external writer of elems[].userdata targets size - 1, so nothing relied
on the pre-zeroing.

Also guard the cap * sizeof(elem) multiplication in
string_list_capacity() and the cap * 2 doubling in the append paths
against size_t overflow.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-09-11 01:41:40 +02:00
1 parent 963b5f06a3
commit 914f0e26f1
2 files changed
+48 -18

No files matched your search

+11 -3
View File
@@ -84,7 +84,10 @@ void linked_list_add(linked_list_t *list, void *value)
if (!list)
return;
new_item = (struct linked_list_item_t *)malloc(sizeof(struct linked_list_item_t));
/* NULL-check malloc: the field writes below would dereference NULL.
* The API returns void, so an OOM leaves the list unchanged. */
if (!(new_item = (struct linked_list_item_t *)malloc(sizeof(struct linked_list_item_t))))
return;
new_item->value = value;
new_item->previous = list->last_item;
new_item->next = NULL;
@@ -116,7 +119,9 @@ void linked_list_insert(linked_list_t *list, size_t index, void *value)
next_item = next_item->next;
}
new_item = (struct linked_list_item_t *)malloc(sizeof(struct linked_list_item_t));
/* NULL-check malloc: see linked_list_add. */
if (!(new_item = (struct linked_list_item_t *)malloc(sizeof(struct linked_list_item_t))))
return;
new_item->value = value;
if (previous_item)
@@ -403,7 +408,10 @@ linked_list_iterator_t *linked_list_iterator(linked_list_t *list, bool forward)
if (!list || !list->first_item)
return NULL;
iterator = (linked_list_iterator_t *)malloc(sizeof(linked_list_iterator_t));
/* NULL-check malloc: callers already handle a NULL iterator
* (returned for an empty list). */
if (!(iterator = (linked_list_iterator_t *)malloc(sizeof(linked_list_iterator_t))))
return NULL;
iterator->list = list;
iterator->item = forward ? list->first_item : list->last_item;
iterator->forward = forward;
+37 -15
View File
@@ -54,16 +54,24 @@ static bool string_list_deinitialize_internal(struct string_list *list)
bool string_list_capacity(struct string_list *list, size_t cap)
{
struct string_list_elem *new_data = (struct string_list_elem*)
struct string_list_elem *new_data;
/* Guard the byte-count multiplication: a huge cap would wrap and
* realloc a buffer far smaller than the caller expects. */
if (cap > SIZE_MAX / sizeof(*new_data))
return false;
new_data = (struct string_list_elem*)
realloc(list->elems, cap * sizeof(*new_data));
if (!new_data)
return false;
if (cap > list->cap)
memset(&new_data[list->cap], 0,
sizeof(*new_data) * (cap - list->cap));
/* Slots in [size, cap) are never read: string_list_free() only walks
* [0, size), and the append paths fully initialise a slot when it
* becomes live. So there is no need to zero the whole new half on
* every doubling, which for large lists was a memset over hundreds of
* thousands of elements to add one entry. */
list->elems = new_data;
list->cap = cap;
return true;
@@ -142,17 +150,24 @@ bool string_list_append(struct string_list *list, const char *elem,
{
char *data_dup = NULL;
if ( list->size >= list->cap
&& !string_list_capacity(list,
if (list->size >= list->cap)
{
if (list->cap > SIZE_MAX / 2)
return false;
if (!string_list_capacity(list,
(list->cap > 0) ? (list->cap * 2) : 32))
return false;
return false;
}
data_dup = strdup(elem);
if (!data_dup)
return false;
list->elems[list->size].data = data_dup;
list->elems[list->size].attr = attr;
list->elems[list->size].data = data_dup;
list->elems[list->size].attr = attr;
/* Slot is not pre-zeroed (see string_list_capacity); userdata must
* be NULL so string_list_free() does not free garbage. */
list->elems[list->size].userdata = NULL;
list->size++;
return true;
@@ -163,10 +178,14 @@ bool string_list_append_n(struct string_list *list, const char *elem,
{
char *data_dup = NULL;
if ( list->size >= list->cap
&& !string_list_capacity(list,
if (list->size >= list->cap)
{
if (list->cap > SIZE_MAX / 2)
return false;
if (!string_list_capacity(list,
(list->cap > 0) ? (list->cap * 2) : 32))
return false;
return false;
}
data_dup = (char*)malloc(len + 1);
if (!data_dup)
@@ -174,8 +193,11 @@ bool string_list_append_n(struct string_list *list, const char *elem,
memcpy(data_dup, elem, len);
data_dup[len] = '\0';
list->elems[list->size].data = data_dup;
list->elems[list->size].attr = attr;
list->elems[list->size].data = data_dup;
list->elems[list->size].attr = attr;
/* Slot is not pre-zeroed (see string_list_capacity); userdata must
* be NULL so string_list_free() does not free garbage. */
list->elems[list->size].userdata = NULL;
list->size++;
return true;
}