core_info: gate string_split on strdup success in CCJSONStringHandler

CCJSONStringHandler in core_info.c had an unchecked strdup
whose result fed directly into string_split, which
NULL-derefs on NULL input:

    *pCtx->current_string_val = strdup(pValue);

    if (pCtx->current_string_list_val)
    {
       if (*pCtx->current_string_list_val)
          string_list_free(*pCtx->current_string_list_val);
       *pCtx->current_string_list_val =
          string_split(*pCtx->current_string_val, "|");
    }

On strdup OOM, *current_string_val = NULL.  string_split
in libretro-common/lists/string_list.c does:

    struct string_list *string_split(const char *str, const char *delim)
    {
       const char *p = str;
       struct string_list *list = string_list_new();
       if (!list)
          return NULL;

       while (*p)   /* NULL-deref on str == NULL */
       {
          ...

There's no NULL-check on str before dereferencing via p.  So
strdup OOM -> string_split segfault.

Fix: gate the entire string_split block on
'*pCtx->current_string_val' being non-NULL.  Consequence on
OOM: the string_list for this field is not updated
(*current_string_list_val retains its previous value, which
could be NULL if it was never set or a valid list if this
field has been parsed before - either is fine for downstream
consumers, which already NULL-guard the list per the deep-
copy pattern in core_info_copy).

=== Swept-clean in the same pass across small modules ===

- camera/drivers/video4linux2.c (3 sites), pipewire.c (2
  sites), ffmpeg.c (1), android.c (1): all NULL-checked.

- record/record_driver.c (1 site) and record/drivers/
  record_wav.c / record_ffmpeg.c (1 each): all NULL-checked.

- location/drivers/android.c (1 site): NULL-checked.

- bluetooth/drivers/bluez.c / bluetoothctl.c (1 each):
  return calloc() directly without NULL-check, but the caller
  in bluetooth/bluetooth_driver.c does
    bt_st->data = bt_st->drv->init();
    if (!bt_st->data)
       bt_st->active = false;
  so the NULL return is handled - these driver init functions
  are intentionally minimal.

- disk_control.c: zero alloc sites.

- disk_index_file.c (1 site): strdup in DCifJSONStringHandler,
  but downstream readers NULL-guard
  context.image_path, so strdup failure produces empty
  image_path - graceful degradation, not a crash.

- core_info.c other 44 sites: all NULL-checked.  Includes the
  deep-copy strdups in core_info_copy which use the conditional
  '? strdup(src->x) : NULL' pattern (strdup failure produces
  NULL, downstream tolerates).  core_info_cache_list_new has a
  two-stage alloc with proper cleanup.  core_info_path_list_
  new has bulk NULL-check after four parallel callocs.

- core_updater_list.c (14 sites): all NULL-checked, proper
  tmp-pattern reallocs for each remote/local path buffer.

- core_backup.c (4 sites): all NULL-checked.

Reachability: strdup OOM in JSON parsing is rare but possible.
This code path fires during core info parsing at startup - a
failure here would previously crash the core scanner silently
on memory-starved devices; post-patch the scanner continues
with a missing list field.

Thread-safety: core info parsing runs on the core scanner
task thread, single-threaded per scan.  No concurrency
concerns.
This commit is contained in:
U-DESKTOP-SPFP6AQ\twistedtechre committed 2026-04-24 16:02:32 +02:00
1 parent 8fd7aa48c6
commit 4e5f06b06b
1 file changed
+9 -1
+9 -1
View File
@@ -307,7 +307,15 @@ static bool CCJSONStringHandler(void *context,
free(*pCtx->current_string_val);
*pCtx->current_string_val = strdup(pValue);
if (pCtx->current_string_list_val)
/* Gate the string_list split on strdup success: if strdup
* returned NULL (OOM), the assignment above stored NULL
* into *current_string_val, and string_split dereferences
* its first parameter unconditionally at
* libretro-common/lists/string_list.c:252 ('while (*p)' with
* p = str). On OOM just leave current_string_list_val
* alone; downstream core_info readers already tolerate
* missing split lists. */
if (pCtx->current_string_list_val && *pCtx->current_string_val)
{
if (*pCtx->current_string_list_val)
string_list_free(*pCtx->current_string_list_val);