From 4e5f06b06be9f7bf045268dec54d8e46d7c5b564 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 24 Apr 2026 16:02:15 +0200 Subject: [PATCH] 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. --- core_info.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core_info.c b/core_info.c index 6c2f32358a..fb8658bc94 100644 --- a/core_info.c +++ b/core_info.c @@ -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);