Commit 1c7e114
committed
menu + file_list: NULL-check init_item_file strdups, contentless_cores entry, and menu_explore arena
Three related OOM / NULL-deref fixes found during a menu
subsystem sweep. All share the shape 'alloc/strdup ->
immediate deref without guard' and have been triggered or are
reachable via menu-driven code paths.
=== libretro-common/lists/file_list.c: init_item_file strdup(NULL) ===
static INLINE void init_item_file(struct item_file *item,
const char *path, const char *label, unsigned type,
size_t directory_ptr, size_t entry_idx)
{
item->path = strdup(path); /* strdup(NULL) = UB */
item->label = strdup(label); /* strdup(NULL) = UB */
...
strdup(NULL) is undefined behaviour - glibc crashes, musl
returns NULL, some platforms segfault. This is reachable
because callers of file_list_insert (the only caller of
init_item_file) can pass a NULL path.
Verified via:
* menu/menu_driver.c:4224 menu_entries_prepend calls
file_list_insert(list, path, label, ...) with path coming
unchecked from callers in menu/menu_displaylist.c.
* menu_displaylist.c call sites (lines 303, 311, 318, 326
etc.) pass msg_hash_to_str(...) as path. msg_hash_to_str
is defined to return NULL for enum values no language
handler recognises (msg_hash.c:584: 'const char *ret =
NULL;' + fallthrough default).
The sibling file_list_append at line 117 of the same file
already does the right thing ('if (path) item->path =
strdup(path);'). This patch brings init_item_file in line.
Fix: NULL-gate both strdup calls.
=== menu/menu_contentless_cores.c: entry malloc unchecked ===
contentless_core_info_entry_t *entry =
(contentless_core_info_entry_t*)malloc(sizeof(*entry));
size_t _len = strlcpy(licenses_str, ...); /* unrelated */
...
entry->licenses_str = strdup(licenses_str); /* NULL-deref on OOM */
entry->runtime.runtime_str = NULL;
...
RHMAP_SET_STR(state->info_entries, core_info->core_file_id.str, entry);
Unchecked malloc; the entry->* field writes below NULL-deref
on OOM. Fires during contentless cores menu population - one
entry per core that supports SUPPORTS_NO_GAME, typically
hundreds of allocations over the enumeration loop.
Fix: 'if (!entry) continue;' at the top of the if-block, so
the outer for-loop over core_info_list continues to the next
core. User-visible effect on OOM is that the failing core
doesn't appear in the contentless-cores list; picked up on
the next menu refresh.
=== menu/menu_explore.c: ex_arena_grow malloc unchecked ===
static void ex_arena_grow(ex_arena *arena, size_t min_size)
{
size_t _len = EX_ARENA_ALIGN_UP(...);
arena->ptr = (char *)malloc(_len);
arena->end = arena->ptr + _len; /* UB on NULL */
RBUF_PUSH(arena->blocks, arena->ptr); /* pushes NULL */
}
static void *ex_arena_alloc(ex_arena *arena, size_t len)
{
void *ptr = NULL;
if (len > (size_t)(arena->end - arena->ptr))
ex_arena_grow(arena, len);
ptr = arena->ptr; /* stale NULL */
...
return ptr;
}
Arena allocator backing the Explore menu's string/index store.
On OOM malloc returns NULL; the current code computes
'arena->end = NULL + _len' (pointer arithmetic on NULL is UB)
and pushes NULL into the blocks RBUF. ex_arena_alloc then
returns NULL to the three callers (~line 376 for new
explore_string_t, ~line 797 for e->original_title, ~line 810
for e->split), each of which does an immediate memcpy into
the returned pointer and NULL-derefs.
Fix: three-part change:
1. ex_arena_grow: malloc-to-tmp; on failure return without
mutating arena->ptr/end/blocks, so the arena stays in its
pre-grow (exhausted but valid) state.
2. ex_arena_alloc: re-check capacity after ex_arena_grow; if
still insufficient (i.e. grow failed), return NULL so
callers can bail.
3. Three callers: NULL-gate the memcpy / continue as
appropriate. The for-loop in explore_add_unique_string
skips one indexing entry; the EXPLORE_SHOW_ORIGINAL_TITLE
and split blocks leave their fields at their pre-
initialised NULL values.
The alternative (making ex_arena_grow bool and propagating
through ex_arena_alloc's signature) would have been cleaner
but churns every call site and the internal ex_arena type, so
sticking with NULL-propagation minimises the diff while
covering the crash path.
=== Not a bug, verified clean during this pass ===
* menu/menu_displaylist.c - zero raw alloc sites; uses
string_list_new, file_list_* wrappers throughout. All
wrappers already NULL-tolerate their results.
* menu/menu_driver.c - 9 alloc sites, all NULL-checked
(several already fixed in earlier commits in this series).
* menu/menu_explore.c:473 (explore_state_t calloc) - NULL-
checked with early-return.
* libretro-common/lists/file_list.c:file_list_reserve -
realloc-to-tmp with NULL-check, clean.
* libretro-common/lists/file_list.c:file_list_append -
NULL-gates strdup on path / label (the pattern this patch
brings init_item_file in line with).
=== Out of scope ===
RBUF_PUSH itself has latent OOM issues: rbuf__grow returns
unchanged buf on realloc failure with capacity unbumped, so the
'(b)[RBUF__HDR(b)->len++] = val' push writes past the allocated
end. On first-time PUSH (buf == NULL) it can also deref NULL
via RBUF__HDR. Fixing this is a libretro-common-wide change
affecting every RBUF user in the tree; out of scope for this
patch. The arena fix above avoids the realloc-failure path
for this one caller by skipping RBUF_PUSH entirely when malloc
fails.
=== Thread-safety ===
All three fix sites are invoked on the menu/main thread during
driver init, menu rebuilds, or user-driven list refreshes. No
shared-state mutation changes; no lock discipline changes.
=== Reachability ===
* init_item_file: every menu list population (frequent, every
navigation).
* contentless_cores: menu open with contentless cores present
in the core list.
* ex_arena_grow: first time the Explore menu is opened with
a non-trivial playlist collection, then on every rescan.1 parent 4cf2d21 commit 1c7e114
3 files changed
Lines changed: 61 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
79 | 79 | | |
80 | 80 | | |
81 | 81 | | |
82 | | - | |
83 | | - | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
84 | 90 | | |
85 | 91 | | |
86 | 92 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
122 | 122 | | |
123 | 123 | | |
124 | 124 | | |
125 | | - | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
126 | 137 | | |
127 | 138 | | |
128 | 139 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
188 | 188 | | |
189 | 189 | | |
190 | 190 | | |
191 | | - | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
192 | 204 | | |
193 | 205 | | |
194 | 206 | | |
| |||
198 | 210 | | |
199 | 211 | | |
200 | 212 | | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
201 | 219 | | |
202 | 220 | | |
203 | 221 | | |
| |||
357 | 375 | | |
358 | 376 | | |
359 | 377 | | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
360 | 386 | | |
361 | 387 | | |
362 | 388 | | |
| |||
770 | 796 | | |
771 | 797 | | |
772 | 798 | | |
773 | | - | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
774 | 807 | | |
775 | 808 | | |
776 | 809 | | |
| |||
782 | 815 | | |
783 | 816 | | |
784 | 817 | | |
785 | | - | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
786 | 824 | | |
787 | 825 | | |
788 | 826 | | |
| |||
0 commit comments